How often does the AngularJS digest loop run?

Problem

When discussing the merits of AngularJS, two-way data binding is often touted as a major benefit of Angular over other JS frameworks. Digging deeper, the documentation suggests this process is done through dirty-checking rather than through event-driven measures. At first, it seems that the digest-loop works by having a method fire off in the background at periodic intervals, checking all the $watches during each cycle. However, reading further, it seems that the digest-loop is actually triggered by rootScope.digest(), which in turn is triggered by $.apply, which is in turn triggered by an event(!), such as an onClick event called through ng-click.

But, how can this be? I thought Angular doesn't use change listeners. So how does the digest-loop really operate? Does Angular automatically kick-off the digest-loop internally, or is the digest-loop triggered by events? If the digest-loop is run automatically, how often does it run?


Some clarification points:

  • I'm not asking about how the digest-loop runs when manually binding to changes. In this case, if you want to force a digest loop, you can do so by calling $.apply()
  • I'm also not asking about how often digest loop runs in response to user events. For example, if ng-model is on an input box, Angular will kick-off a digest loop when a user starts typing. The confusing part is that in order to know a user was typing, doesn't Angular use an event-based onKeyUp somewhere?
  • I already know that there is a limit of 10 cycles max per digest-loop. My question is less about the number of cycles per digest-loop, but rather the number of digest-loops that run, say, per second.
  • Bonus questions: How does the digest-loop relate to the JavaScript event-loop? Does the JS event loop run periodically in the background? Is the digest-loop the same thing as the event loop, but only in the "Angular Context"? Are these totally different ideas?
Problem courtesy of: derekchen14

Solution

Angular digests are triggered - they don't happen through polling.

Code executes, after code is done, angular triggers a digest.

Example:

 element.on('click', function() {
     $scope.$apply(function() { 
         // do some code here, after this, $digest cycle will be triggered
     });
 });

Angular will also trigger a $digest after the compile/link phase:

Compile > Link > Digest

And as to how many digest cycles are triggered? It depends on how soon the scope variables stabalise. Usually it takes at least 2 cycles to determine that.

Solution courtesy of: pixelbits

Discussion

Short direct answer to the main question is "NO", angular doesn't automatically trigger digest loop.

TL;DR answer:

Digest loop is designed to run dirty check over POJO models associated with Angular scope instances, so it only needs to run when a model might be changed. In a single page Web application running inside Browser, the following actions/events could lead to a model change

  1. DOM events
  2. XHR responses firing callbacks
  3. Browser's location changes
  4. Timers (setTimout, setInterval) firing the callbacks

Correspondingly, Angular trigger digest loop at, for instance

  1. input directives+ngModel, ngClick, ngMouseOver etc.
  2. $http and $resource
  3. $location
  4. $timeout

Try to answer those bonus questions from my understanding:

  1. ngModel directive often used with angular input directives (text, select etc) together, and the laters will listen on "change" events and call $setViewValue API exposed from ngModelController in order to sync back dom value. During the sync process, ngModelController will make sure to trigger digest loop.
  2. digest loop is different from JS event loop, the later is concept of JS runtime (checkout the great visualised session https://www.youtube.com/watch?v=8aGhZQkoFbQ) which run against event queue and remove consumed event from the queue automatically, but digest loop never remove watch from its watch list unless you explicitly unwatch.
  3. the number of digest loops per second depends on the efficiency of all watch callbacks being executed through the loop . If some bad code took one second to finish, then this digest loop would cost more than 1 sec.

So some key practices for avoid angular performance pitfalls are

  1. Watch callback should be coded as simpler/efficient as possible, for example detach complicated algorithm code to, for example, worker threads
  2. Proactively remove a watch if it is not used anymore
  3. Prefer to call $scope.$digest() instead of $scope.$apply() if applicable, $digest() only run part of scope tree and ensure the models associated under the subtree is reflecting to view. But $apply() will run against entire scope tree, it will iterate through more watches.
Discussion courtesy of: Roger Jin

I believe this is what happens. AngularJS made a smart assumption that model changes happen only on user interaction. These interactions can happen due to

  • Mouse activity (move, clicked etc)
  • Keyboard activity (key up, key down etc)

AngularJS directives for the corresponding events wrap the expression execution in $scope.$apply as shown by @pixelbits in his example. This results in digest cycle.

There are some other events too where AngularJS triggers the digest loop. $timeout service and the $interval service are two such examples. Code wrapped in these service also results in digest loop to run. There maybe be some other events\services that can cause digest cycles to execute but these are the major ones.

This is the very reason that changes to model outside the Angular context does not update the watches and bindings. So one needs to explicitly call $scope.$apply. We do it all the time when integrating with jQuery plugins.

Discussion courtesy of: Chandermani

This recipe can be found in it's original form on Stack Over Flow.