Case Study: Using $scope.$digest() As A Performance Optimization In AngularJS
Yesterday, I blogged about using the $scope.$digest() method in AngularJS as an optimization over the more traditional $scope.$apply() method. Since this is a special kind of optimization that cannot be applied in a general manor, I wanted to present a case-study in which the $digest optimization is rather well suited - deferred DOM (Document Object Model) tree transclusion.
In the following demo, I have a list that is being rendered by an ngRepeat directive. Inside each list item, I have a primary container that is always visible and a nested container that is only visible upon mouse-over. The nested container may never be rendered, depending on the user behavior. As such, it seems unnecessary for me to have to initialize the various AngularJS bindings inside of the nested container.
To optimize this situation, I'm going to defer the DOM-injection and the subsequent AngularJS binding of the nested container until the user mouses into the primary container. Then, as a further optimization, when the nested container is injected, I will use the $scope.$digest() method (instead of $scope.$apply()) to initialize the injected DOM. This way, the visibility of the changes will be restricted to the local $scope tree, which will save a ton of processing.
NOTE: I am not advocating this as a general practice; I am presenting this as an optimization WHEN and IF it is needed to provide a more performant user experience.
In the following code, the bnDeferDom directive will compile the HTML before the ngRepeat is linked. This is how we are able to modify the HTML that gets cloned in the list.
In addition to the deferred DOM transclusion, I've also included an ngRepeat controller and an ngClick binding. These latter items are here to present a comparison of the $apply() and $digest() methods. When you click on one of the ngRepeat items, it will invoke the ngClick handler which will, in turn, trigger an $apply(). If you look at the browser's console, you can see the flurry of activity triggered by the $apply(). This is to be held in contrast to the complete lack of activity triggered by the $digest() method.
Most of the time, you'll probably have UIs (User Interfaces) that are small enough to diminish the payoff of such optimizations. But, if you do need to present a lot of data to the user, and still want to adhere to the "Angular Way," this type of optimization can have a very noticeable impact on the performance of the application.
Want to use code from this post? Check out the license.
I wrote a post about $apply and $digest in big projects yesterday. I think we have 2 problems for complicated UI with AngularJS :
- The entire code of AngularJS is wrote to call $apply everywhere. ng- directives, $http, etc.
- In a $digest of a directive, it's not possible to call a $digest in other directives synchronously.
Take a look for examples : https://groups.google.com/forum/#!topic/angular/tswtLq9xbwU
It's a really interesting problem. It seems that the over-arching "magic" of AngularJS is that is "just works." That magic can, depending on the size of an app, come at a price. Sometimes, every dirty check can be expensive. Sometimes a digest can cause unwanted "paint" events in the browser. But, most of the time, it's not a problem.... until it is.
When it becomes a problem, we try to find ways to minimize the impact of the various digests. I'm intrigued by the idea of being able to trigger a digest in different directives synchronously. It's definitely possible, BUT you are the one that has to wire it together.
I'd like to play around with some ideas on the matter and put together a demo. I think the trick will lie in the fact that digests aren't _required_ for events but, rather for much of the rendering.
Time to put on my thinking cap :D
this is the first time I see such behavior on a UI. For me this is a basic principle to let the developer have control of his UI and not re-check everything.
Thank you for your help.
I took at stab at answering your question:
Hope that helps!