Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to know when an Angular.js digest cycle is starting (or finishing)?

Tags:

angularjs

I have an Angular.js app that performs a large set of accounting-type calculations (revenue, costs, profit, etc) across a complex object model. A change to any part of the object model requires all of the calculations to be re-executed. Furthermore, many of the changes to the object model can be made directly from my templates using bindings - there's no need for a controller to intermediate.

Many of the calculation results need to both be displayed to the user and contribute to downstream calculations. For example, revenue is displayed to the user but is also used to calculate the profit (which is also displayed to the user). However, there's no point in calculating the revenue twice (it's a complex calculation) on each digest cycle, so an obvious optimization is to memoize when it is first calculated and then reuse this memoized value for the duration of the cycle.

The problem is that I need to clear these memoizations either at the beginning or end of the digest cycle. One approach would be for the controller to intercept every possible source of changes and manually unmemoize the object model before proceeding with the calculations. Instead, I'd much prefer to just keep using the bindings that have been set up.

As far as I can tell, what I need is a way to know when either the digest cycle is starting, or when it has finished. When I get this notification, I could reset the memoizations.

The Angular $watch documentation says:

If you want to be notified whenever $digest is called, you can register a watchExpression function with no listener. (Since watchExpression can execute multiple times per $digest cycle when a change is detected, be prepared for multiple calls to your listener.)

This is no use to me because clearing the memoizations on each iteration of the digest cycle would defeat the purpose of having them in the first place. I need only one notification per digest cycle.

How can I do this, or is there an alternate approach to solving the problem?

like image 238
Ben Teese Avatar asked Jan 06 '14 12:01

Ben Teese


People also ask

How does Digest cycle works in AngularJs?

Digest cycle is what Angular JS triggers when a value in the model or view is changed. The cycle sets off the watchers which then match the value of model and view to the newest value. Digest cycle automatically runs when the code encounters a directive.

What is Digest loop in AngularJs?

The digest loop is responsible to update DOM elements with the changes made to the model as well as executing any registered watcher functions. The $digest loop is fired when the browser receives an event that can be managed by the angular context. This loop is made up of two smaller loops.

What is Digest function in angular?

It keeps note of changes and then notifies AngularJs Framework to update DOM. Thus at the end of every digest process, DOM is updated. Angular Context is a runtime environment of AngularJs Framework. First digest process performs a dirty check on watches, and checks if there are any modifications.


Video Answer


1 Answers

OK, I think I finally figured something out, so thought I'd answer my own question.

There's a private Angular method called $$postDigest that will callback a function after the end of the next digest cycle. However, it only runs once, and if you try to get it to reschedule itself recursively it'll go into an infinite loop.

To work around this, you can instead use $$postDigest in conjunction with a regular $watch. If you have a function called fn that you want a controller to invoke after every digest cycle, you'd add something like this to the controller function:

...
var hasRegistered = false;
$scope.$watch(function() {
  if (hasRegistered) return;
  hasRegistered = true
  // Note that we're using a private Angular method here (for now)
  $scope.$$postDigest(function() {
    hasRegistered = false;
    fn();
  });
});
...

This is based on a code fragment by Karl Seamon. Obviously it's not ideal to be calling a private method on $scope. Hopefully a $postDigestWatch method along these lines may eventually get added to Angular.

There's been some confusion in the comments on my original question regarding why you'd want to do this sort of thing: if you're still unclear, check out this blog post I wrote that covers my particular use-case.

like image 138
Ben Teese Avatar answered Sep 24 '22 02:09

Ben Teese