Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

AngularJS: $watch with debounce

I have the following html which represents a search field:

<input ng-model-options="{ debounce: 500 }" type="text" ng-model="name">

And the following js:

$scope.$watch('name', function(newVal, oldVal) {
            if(newVal != oldVal) {
                $scope.pageChanged($scope.sort, $scope.name, $scope.sortDirection);
            }
        });

Now, my pageChanged function makes a REST call to my server and returns a list of entites based on the sort and search information (the "name"). Say that my user wants to search for a "Tom". I would like to avoid my application making three rest calls (name="T", name="To", name="Tom").

I tried doing this with debounce, but it seems that watch doesn't work with debounce so I was wondering what would be the best way to implement this with minimal code?

like image 768
NLuburić Avatar asked Dec 25 '22 20:12

NLuburić


2 Answers

You should be using ng-change for this sort of thing instead of wiring up a watch.

<input ng-model-options="{ debounce: 500 }" type="text" ng-model="name" ng-change="modelChanged()">

JS:

var timeout = $timeout(function(){});

$scope.modelChanged = function(){
    $timeout.cancel(timeout); //cancel the last timeout
    timeout = $timeout(function(){
        $scope.pageChanged($scope.sort, $scope.name, $scope.sortDirection);
    }, 500);
};

I'm unfamiliar with debounce, but it might achieve the same thing.

like image 69
Mathew Berg Avatar answered Dec 29 '22 06:12

Mathew Berg


Very interesting Use Case!

Solution:

I solved a similar issue by implementing the debounce function in a service taking inspiration from lodash' implementation (also can find an implementation here (@Pete BD's answer).

// 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;
    };
  };
}]);

Then I included it in my controller/directive containing the $watch and then doing the magic like so (using your code):

$scope.$watch('name', debounce(function(newVal, oldVal) {
   if(newVal != oldVal) {
     $scope.pageChanged($scope.sort, $scope.name, $scope.sortDirection);
   }
}, 500));

DONE!


Case history:

I also tried to do like:

$scope.$watch('name', function(newVal, oldVal) {

   debounce(function() {
     if(newVal != oldVal) {
       $scope.pageChanged($scope.sort, $scope.name, $scope.sortDirection);
     },500)();
});

but without satisfaction because the watch was run twice in 50ms.

like image 42
morels Avatar answered Dec 29 '22 07:12

morels