angularjs disable button on $http/$q calls

前端 未结 9 769
失恋的感觉
失恋的感觉 2021-02-03 10:12

following the DRY principal, i want to write a button directive which keeps button disabled for the duration of $http class.

I want to do this so as to forbid user from

相关标签:
9条回答
  • 2021-02-03 10:59

    Although I would be careful of over-engineering, a way to do this would be by using a custom directive. This directive

    • Accepts an option, passed by attribute, of a function in the scope that must return a promise
    • On click of the button, calls this function, and disables the button
    • On finally of the promise, it re-enables the button

    -

    app.directive('clickAndDisable', function() {
      return {
        scope: {
          clickAndDisable: '&'
        },
        link: function(scope, iElement, iAttrs) {
          iElement.bind('click', function() {
            iElement.prop('disabled',true);
            scope.clickAndDisable().finally(function() {
              iElement.prop('disabled',false);
            })
          });
        }
      };
    });
    

    This can be used on a button as follows:

    <button click-and-disable="functionThatReturnsPromise()">Click me</button>
    

    You can see this in action at http://plnkr.co/edit/YsDVTlQ8TUxbKAEYNw7L?p=preview , where the function that returns the promise is:

    $scope.functionThatReturnsPromise = function() {
      return $timeout(angular.noop, 1000);
    } 
    

    But you could replace $timeout with a call to $http, or a function from any service that returns a promise.

    0 讨论(0)
  • 2021-02-03 11:00

    I like @codef0rmer 's solution because it is centralized--that is there's no additional code needed for each HTTP request, and you just need to check the global progress flag in your HTML. However, using transformResponse to determine when the request has completed is unreliable because the server may not return anything; in that case the handler isn't called and progress is never set back to false. Also, as written that answer doesn't account for multiple simultaneous requests (progress may return false before all requests have completed).

    I've come up a similar solution which uses interceptors to address these issues. You can put it in your Angular app's config function:

    .config(function ($httpProvider) {
        $httpProvider.interceptors.push(function($q, $rootScope) {
            var numberOfHttpRequests = 0;
            return {
                request: function (config) {
                    numberOfHttpRequests += 1;
                    $rootScope.waitingForHttp = true;
                    return config;
                },
                requestError: function (error) {
                    numberOfHttpRequests -= 1;
                    $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                    return $q.reject(error);
                },
                response: function (response) {
                    numberOfHttpRequests -= 1;
                    $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                    return response;
                },
                responseError: function (error) {
                    numberOfHttpRequests -= 1;
                    $rootScope.waitingForHttp = (numberOfHttpRequests !== 0);
                    return $q.reject(error);
                }
            };
        });
    })
    

    Now you can just use the waitingForHttp to disable buttons (or show a "busy" page). Using interceptors gives an added bonus that now you can use the error functions to log all HTTP errors in one place if you want.

    0 讨论(0)
  • 2021-02-03 11:03

    I was trying different approaches including directives and came up with a simple filter that transforms promise to status object. You can then use the status object to disable related button (or anything else) as well as show some progress messages. So the filter code is:

    function promiseStatus() {
      return function(promise) {
        var status = {
          inProgress: true,
          resolved: false,
          rejected: false
        };
        promise
          .then(function() {
            status.resolved = true;
          })
          .catch(function() {
            status.rejected = true;
          })
          .finally(function() {
            status.inProgress = false;
          });
        return status;
      }
    }
    

    Suppose you have controller like this

    function SubmitController($http) {
      var vm = this;
    
      vm.submitData = function() {
        return $http.post('....')
          .then(function() {
            //Doing something usefull
          });
      }
    }
    

    Then the template will be like this

    <button 
      ng-disabled='status.inProgress' 
      ng-click='status = (vm.submitData() | promiseStatus)'>Submit</button>
    <span class='ng-hide' ng-show='status.inProgress'>Submitting...</span>
    <span class='ng-hide' ng-show='status.resolved'>Submitted succsssfully!</span>
    <span class='ng-hide' ng-show='status.rejected'>Failed to submit!</span>
    

    Full source code is on a Github

    0 讨论(0)
  • 2021-02-03 11:04

    Why not just make it easier.

    <button ng-click="save()" ng-disabled="isProcessing">Save</button>
    
    
    $scope.save = function(){
      $scope.isProcessing = true;
      $http.post('Api/Controller/Save', data).success(
        $scope.isProcessing = false;
      );
    }
    

    Sure it's the case if you need this logic in very few places across your app.

    If you have such logic repeating many times (and if you are not lazy :) ), so in order to follow SOLID principles it definetely better to wrap this functionality into directive (check out other answers for this question to see examples of such directive).

    0 讨论(0)
  • 2021-02-03 11:05

    You could also consider setting a flag, and using the html tag fieldset and ng-disabled. Then you can control how long the yourDisableFlag is true based on $http calls, a $timeout, etc.

    <form name="myForm">
      <fieldset ng-disabled="yourDisableFlag">
        <button id="b1" ng-click="b1function">B1</button>
        <button id="b2" ng-click="b2function">B2</button>
        <button id="b3" ng-click="b3function">B3</button>
      </fieldset>
    </form>
    
    0 讨论(0)
  • 2021-02-03 11:09

    I've prepared directive on plnkr which overrides default ngClick directive

    <button ng-click="loadData($notify)">submit</button>
    

    and controller:

    $scope.loadData = function($notify) {
        $timeout(function() {
          $notify && $notify();
        }, 1000);
      }
    
    0 讨论(0)
提交回复
热议问题