Delaying the $digest() cycle in AngularJS

When scaling an AngularJS app, large data sets can cause the $digest() cycle to run slowly. There are a number of performance optimizations you can make, such as being careful with $watch() and $filter(), using one-time binding, or hunting down expensive operations with Batarang. But sometimes, even with these improvements, an app with a lot of data can feel sluggish due to the $digest() cycle. A classic example is keyboard input. If you have a lot of data and your $digest() cycle is getting kicked off every time a user enters a character into an input or textarea, this causes the processor to go into overdrive when someone is typing rapidly or holding down the backspace button. And then you see significant delays in the text updating on the screen.

The best thing that I’ve found for dealing with this issue is to delay the $digest() cycle using a debounce function. What’s a debounce function, you ask? Well, John Hann wrote a great explanation and implementation using pure JS way back in the late 2000’s. Years later, people ported this pattern to custom angular services. These were more “angularish”, but added a little clutter to your code. But now, Angular 1.3 has officially added support for debounce, so delaying the digest cycle is super easy to do.

TL;DR A debounce function is a function that, as long as it continues to be invoked, will not be triggered. It will only be called after it stops being called for X milliseconds.

For example, you can use a debounce function of 150ms on a text input to prevent the $digest() cycle from being triggered until someone stops entering text for 150 milliseconds. And with Angular’s new native support, it is easier than ever. All you need is a single HTML attribute and you can delay the $digest() cycle like this:

So what is the best debounce delay in milliseconds? Well, it depends on how heavy your $digest() cycle is. On the one hand, the shorter your debounce, the shorter the delay after someone starts typing to when the $digest() fires off and completes. This will determine the perceived responsiveness of the app. Too long, and your app will no longer seem snappy. But on the other hand, if your debounce period is too short, the $digest() cycle will get kicked off too often, which will cause an even greater perceived slowness in your application. You’ll have to figure out what works best for your application.

Happy optimizing!

  • Jesse Gavin

    The other neat thing about ngModelOptions is that these settings will propagate to child elements using ngModel. This way you can set a debounce for an entire with one declaration (as an example).

  • sandro

    I found that not using Angular is the best optimization of all.

  • Josh Ribakoff

    @Sandro – so you tried out Angular, and then switched to something else? Or have you never given it a try, perhaps because you ‘just dislike’ it? Just curious if there’s some alternative MVC framework you found to be more performant – or if you are just hating on MVC frameworks themselves?

  • Daniell0gda

    Yea, just jQuery everything !

  • Sandro Curcio

    A framework like AngularJS exists to get your job done. It’s just a tool. AngularJS is a good tool to do the job it was invented for. If you use the wrong tool for your job, then it’s not the tool’s fault…

  • Stepan Suvorov

    Как было отмечено в предыдущем пункте: AngularJS довольно производительный уже “из коробки”. Но когда число вотчеров превысит 2000, это может стать реальной проблемой. (Это собираются пофиксить в AngularJS 1.3, более подробно можно почитать тут:

    English Translation:
    As noted in the previous paragraph: AngularJS already quite productive “out of the box.” But when the number exceeds 2000 votcherov, it may become a real problem. (This is going to pofiksit AngularJS 1.3, more details can be read here:

    • Jonathan

      and we don’t want too many votcherov, better pofiksit this asap! (sorry)