Over a million developers have joined DZone.
Platinum Partner

Throttling Input in AngularJS Applications using Underscore.js Debounce

· Web Dev Zone

The Web Dev Zone is brought to you in partnership with Mendix.  Discover how IT departments looking for ways to keep up with demand for business apps has caused a new breed of developers to surface - the Rapid Application Developer.

There are numerous scenarios to throttle input so that you aren’t reevaluating your filters every time they change. The more appropriate term is “debounce” because essentially you are waiting for the input to settle before you invoke a function, so you stop bouncing to the server. The canonical case would be a user entering input into a text box to filter a list. If your filter involves some overhead (for example, it is implemented using a REST resource that executes a query on a backend database) you don’t want to keep rerunning and reloading the results while the user is typing. Instead, you want to wait for them to finish typing their filter and then perform the task once.

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

Let’s assume you have a list ($scope.list) that you expose as a filtered list ($scope.filteredList) based on anything that contains the text typed into $scope.searchText. Your form would look something like this (ignore the throttle checkbox for now):

<div data-ng-app='App'>
    <div data-ng-controller="MyCtrl">
            <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>
        <ul><li data-ng-repeat="item in filteredList">{{item}}</li></ul>

The typical scenario is to watch the search text and react instantly. This method handles the filter:

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

The controller asks the scope to $watch like this:

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

This will fire every time you type. To settle things down, use the built-in debounce function that comes with UnderscoreJs. The function is simple: pass it a function to debounce with a time in milliseconds. It will delay actually calling the function you pass until at least the time delay has passed since the last time it was passed. In other words, if we use 1 second (which I did in this example to exaggerate the effect) and the function is called repeatedly as I’m typing in the search box, it will not actually fire until I stop typing and wait for at least 1 second.

You may be tempted to simply debounce the filter action like this:

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

However, this poses a problem. The debounce uses a timer, which ends up outside of Angular’s digest loop, so nothing will be reflected in the UI because Angular doesn’t know about it. Instead, you must wrap it in a call to $apply:

var filterDelayed = function($scope) {

Then you can watch it and only react once the input settles:

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

Of course the full example provides a throttle so you can see the difference between the “instant” filtering and the delayed filtering. The fiddle for this again is online at: http://jsfiddle.net/nZdgm/


The Web Dev Zone is brought to you in partnership with Mendix.  Learn more about The Essentials of Digital Innovation and how it needs to be at the heart of every organization.


Published at DZone with permission of Jeremy Likness , DZone MVB .

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}