Using $scope.$digest() As A Performance Optimization In AngularJS
The other day, I was listening to Brian Genisio, on the Front-End Developer Cast, when I heard him draw a distinction between the $apply() method and the $digest() method in AngularJS. The $apply() method will trigger watchers on the entire $scope chain whereas the $digest() method will only trigger watchers on the current $scope and its children. Definitely a specialized case - but, this struck me as an opportunity for some micro-optimizations in my AngularJS applications.
Since AngularJS is driven by dirty-data checks, it has to run through all of its watchers whenever it thinks the view-model has changed. Then, it has to run through all of its watchers again, to make sure that the last iteration of watchers didn't change the view-model. Then it has to do this again to - ... you get the point. It's an awesome approach; but, when you have a lot of $watch bindings, it can require a lot of [sometimes unnecessary] processing.
The $digest() method can offer an optimization in the dirty-data lifecycle in situations where you know - for a fact - that local changes will not have global implications. In such situations, the $digest() method can limit the scope of processing (no pun intended) to the local branch of the application's $scope chain.
To demonstrate this, I've created a small AngularJS application that uses two different mouse-interaction directives. On one element, it uses the native ngMouseEnter and ngMouseLeave directives; on another element, it uses a custom bnDigest directive which handles the mouse-interaction logic more manually.
To illustrate the point, the bnDigest directive has to create a child scope. Otherwise, calling the $digest() method would be tantamount to calling the $apply() method and you wouldn't be able to see the distinction.
As you can see, the bnDigest updates the "localIsHot" view-model value and then calls the $digest() method. This works because the bnDigest directive has intimate knowledge of how the localIsHot view-model is being used; and, it knows that none of the higher-up $scope objects need to know about it. As such, it can safely trigger a local-only digest without leaving the view in a partially-rendered state.
As you're probably thinking, this requires the directive to know a lot about how it's being used. That's right. That's why this is not a general optimization but, rather, an optimization that can only be used when a directive is acting more like a "view helper" and less like a general event-binding. That said, in some types of situations, this can be an easy-to-implement performance optimization.
Want to use code from this post? Check out the license.
Oh that's a nice catch, I'll definitively use it next time I can :)
Thanks my man!
Good one. Definitively recommended $digest in place of $apply.
It's a small optimization, and it doesn't make sense all the time. But, in some situations, definitely a boost.
Agreed Ben. Definitely Boost in some cases where we can use.
As a follow-up to this post, I created a concrete case-study of when the $digest() approach really makes sense:
Great find! I have lots of drag & drop on my web app which has the potential to trigger many 'applies' per second, and even after optimizing heavily it could still feel a bit slow at times. There's a lot of stuff going on on the page so being able to digest only a certain scope during drag & drop eliminated any perceived lag.
I am loving your articles, you go much deeper into the topics that really matter when truly understanding a lot of the "magic" behind angular. I agree with you that this is a great trick for *spot* optimizations..however I have lately seen it crop up in a couple open source libraries and that really feel far too limiting for their use case, so I'd like to reiterate that you should only optimize when it really matters. (especially when limiting the scope of the digest directly limits the access of directives/controllers up the chain)
Here is the example that seems to show that also $scope.$digest() updates
outside scopes. Or did I miss anything?
And now just pass the $digest of your directive/controller to a service/provider/facotry/whatever, and you have the capacity to make a local update of your view when you have an aync model change.
And to be perfect, change all ng-click, ng-mouseover ... ng-IFireAScopeApply to digest-click, digest-mouseover ...
Or use broadcast service => directive/controller, each can make the $digest if it's needed, this solution has the advantage to prevent coupling.
Inspired by you and angular source, event without $apply :
Nice article sir . I also tried to write on this may be it will help someone