⬆️ ⬇️

Input throttling on AngularJS using debounce

There are different scenarios for using throttling input so that the filter values ​​will not be recalculated every time when the value changes, but less often. A more appropriate term is “debounce”, since in essence you expect the value to stabilize at any constant level before calling the function so as not to cause a “bounce” of constant requests to the server. A canonical case of this kind is a user who enters text into an input box to filter the list of items. If the logic of your filter includes some overhead (for example, filtering occurs through a REST resource that performs a query on the backend database), then you definitely do not want to restart and reload the query results all the time while the user writes text in the field. It would be more correct to wait instead for it to finish, and only after that execute the query once.



A simple solution to this problem is here: jsfiddle.net/nZdgm



Imagine that you have a list ($ scope.list) that you publish as a filtered list ($ scope.filteredList) based on something that contains text from the $ scope.searchText field. Your form would look something like this (ignore the throttle checkbox for now):

')

<div data-ng-app='App'> <div data-ng-controller="MyCtrl"> <form> <label for="searchText">Search Text:</label> <input data-ng-model="searchText" name="searchText" /> <input type="checkbox" data-ng-model="throttle"> Throttle <label>You typed:</label> <span>{{searchText}}</span> </form> <ul><li data-ng-repeat="item in filteredList">{{item}}</li></ul> </div> </div> 




A typical scenario is to observe the search field and respond instantly. Filtering method:



 var filterAction = function($scope) { if (_.isEmpty($scope.searchText)) { $scope.filteredList = $scope.list; return; } var searchText = $scope.searchText.toLowerCase(); $scope.filteredList = _.filter($scope.list, function(item) { return item.indexOf(searchText) !== -1; }); }; 




The controller sets $ watch like this:



 $scope.$watch('searchText', function(){filterAction($scope);}); 




This approach will start filtering each time you type in the field. To fix the situation, we use the debounce function built into underscore.js. The function is pretty simple: pass it the function to execute and the time in milliseconds. This will delay the actual function call until the specified time has passed since the last attempt to call it. In other words, with a delay of 1 second (which I use in this example to exaggerate the effect) and a continuous stream of function calls during fast text entry in the field, the real function will not be called until I stop typing and from that moment 1 second will not pass.



It may be tempting to do a simple debounce like this:



 var filterThrottled = _.debounce(filterAction, 1000); $scope.$watch('searchText', function(){filterThrottled($scope);}); 




However, there is a problem. This approach uses a timer that works outside the $ digest cycle , so this will not ultimately affect the UI, because Angular does not know about the changes that have occurred. Instead, you must wrap the call in $ apply :



 var filterDelayed = function($scope) { $scope.$apply(function(){filterAction($scope);}); }; 




After that, you can set $ watch and respond as soon as the input stops:



 var filterThrottled = _.debounce(filterDelayed, 1000); $scope.$watch('searchText', function(){filterThrottled($scope);}); 




Of course, a full-fledged example here should include throttling, so that you can see the difference between “instant” filtering and deferred. Fiddle in case you can see here: jsfiddle.net/nZdgm

Source: https://habr.com/ru/post/210344/



All Articles