How to write a debounce service in AngularJS

前端 未结 7 665
说谎
说谎 2020-11-27 11:01

The underscore library provides a debounce function that prevents multiple calls to a function within a set period of time. Their version makes use of setTimeout.

H

相关标签:
7条回答
  • 2020-11-27 11:37

    Pete BD gave a good start to the debounce service, however, I see two problems:

    1. returns when you should send in a work() callback that uses javascript closure if you need to change state in the caller.
    2. timeout variable - isn't that timeout variable a problem? timeout[] maybe? imagine 2 directives using debounce - signalr, input form validator, I believe the factory approach would break down.

    What I am currently using:

    I changed factory to a service so each directive gets a NEW instance of debounce aka new instance of the timeout variable. - i haven't ran into a situation where 1 directive will need timeout to be timeout[].

    .service('reactService', ['$timeout', '$q', function ($timeout, $q) {
        this.Debounce = function () {
            var timeout;
    
            this.Invoke = function (func, wait, immediate) {
                var context = this, args = arguments;
                var later = function () {
                    timeout = null;
                    if (!immediate) {
                        func.apply(context, args);
                    }
                };
                var callNow = immediate && !timeout;
                if (timeout) {
                    $timeout.cancel(timeout);
                }
                timeout = $timeout(later, wait);
                if (callNow) {
                    func.apply(context, args);
                }
            };
            return this;
        }
    }]);
    

    in my angularjs remote validator

        .directive('remoteValidator', ['$http', 'reactService', function ($http, reactService) {
            return {
                require: 'ngModel',
                link: function (scope, elm, attrs, ctrl) {
                    var newDebounce = new reactService.Debounce();
    
                    var work = function(){
    //....
                    };
    
                    elm.on('blur keyup change', function () {
                       newDebounce.Invoke(function(){ scope.$apply(work); }, 1000, false);
                    });
                }
            };
        }])
    
    0 讨论(0)
  • 2020-11-27 11:39

    Angular 1.3 has debounce as standard

    Worth mentioning that debounce comes built in with Angular 1.3. As you'd expect, it's implemented as a directive. You can do this:

    <input ng-model='address' ng-model-options="{ debounce: 500 }" />
    

    The $scope.address attribute is not updated until 500ms after the last keystroke.

    If you need more control

    If you want more granularity, you can set different bounce times for different events:

    <input ng-model='person.address' ng-model-options="{ updateOn: 'default blur', debounce: {'default': 500, 'blur': 0} }" />
    

    Here for example we have a 500ms debounce for a keystroke, and no debounce for a blur.

    Documentation

    Read the documentation here: https://docs.angularjs.org/api/ng/directive/ngModelOptions

    0 讨论(0)
  • 2020-11-27 11:42

    Here is a working example of such a service: http://plnkr.co/edit/fJwRER?p=preview. It creates a $q deferred object that will be resolved when the debounced function is finally called.

    Each time the debounce function is called the promise to the next call of the inner function is returned.

    // Create an AngularJS service called debounce
    app.factory('debounce', ['$timeout','$q', function($timeout, $q) {
      // The service is actually this function, which we call with the func
      // that should be debounced and how long to wait in between calls
      return function debounce(func, wait, immediate) {
        var timeout;
        // Create a deferred object that will be resolved when we need to
        // actually call the func
        var deferred = $q.defer();
        return function() {
          var context = this, args = arguments;
          var later = function() {
            timeout = null;
            if(!immediate) {
              deferred.resolve(func.apply(context, args));
              deferred = $q.defer();
            }
          };
          var callNow = immediate && !timeout;
          if ( timeout ) {
            $timeout.cancel(timeout);
          }
          timeout = $timeout(later, wait);
          if (callNow) {
            deferred.resolve(func.apply(context,args));
            deferred = $q.defer();
          }
          return deferred.promise;
        };
      };
    }]);
    

    You get the return value from the debounced function by using the then method on the promise.

    $scope.addMsg = function(msg) {
        console.log('addMsg called with', msg);
        return msg;
    };
    
    $scope.addMsgDebounced = debounce($scope.addMsg, 2000, false);
    
    $scope.logReturn = function(msg) {
        console.log('logReturn called with', msg);
        var promise = $scope.addMsgDebounced(msg);
        promise.then(function(msg) {
            console.log('Promise resolved with', msg);
        });
    };
    

    If you call logReturn multiple times in quick succession you will see the logReturn call logged over and over but only one addMsg call logged.

    0 讨论(0)
  • 2020-11-27 11:46

    Support for this has landed in angularjs#1.3.0.beta6 if you're dealing with a model interaction.

    https://docs.angularjs.org/api/ng/directive/ngModelOptions

    0 讨论(0)
  • 2020-11-27 11:51

    https://github.com/capaj/ng-tools/blob/master/src/debounce.js

    usage:

    app.directive('autosavable', function(debounce) {
        return {
            restrict : 'A',
            require : '?ngModel',
            link : function(scope, element, attrs, ngModel) {
                var debounced = debounce(function() {
                    scope.$broadcast('autoSave');
                }, 5000, false);
    
                element.bind('keypress', function(e) {
                    debounced();
                });
            }
        };
    });
    
    0 讨论(0)
  • 2020-11-27 11:56

    Since I've written the comments above I've had a bit of a change of heart on this.

    The short answer is, you shouldn't need to debounce functions that return values.

    Why? Well, philosophically I think it makes more sense to keep debouncing for events and only for events. If you have a method that returns a value that you'd like to debounce, you should instead debounce the event that causes your method to run downstream.

    0 讨论(0)
提交回复
热议问题