HTML5 Zone is brought to you in partnership with:

Jeremy Likness was named Silverlight MVP of the Year in 2010. Now Senior Consultant and Technical Project Manager for Wintellect, LLC, he has spent the past decade building highly scalable web-based commercial solutions using the Microsoft technology stack. He has fifteen years of experience developing enterprise applications in vertical markets including insurance, health/wellness, supply chain management, and mobility. He is the creator of the popular MVVM framework Jounce and an open source Silverlight Isolated Storage Database System called Sterling. Likness speaks and blogs frequently on Silverlight, MEF, Prism, Team Foundation Server, and related Microsoft technologies. Jeremy is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

Throttling Input in AngularJS Applications using Underscore.js Debounce

11.08.2013
| 4933 views |
  • submit to reddit

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">
        <form>
            <label for="searchText">Search Text:</label>
            <input data-ng-model="searchText" name="searchText" />
            <br/>
            <input type="checkbox" data-ng-model="throttle"> Throttle
            <br/>
            <label>You typed:</label> <span>{{searchText}}</span>
        </form>
        <ul><li data-ng-repeat="item in filteredList">{{item}}</li></ul>
    </div>
</div>

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;
        return;
    }
    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) {
    $scope.$apply(function(){filterAction($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/

Enjoy!

Published at DZone with permission of Jeremy Likness, author and DZone MVB. (source)

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)