Skip to main content
Ben Nadel at RIA Unleashed (Nov. 2010) with: David Crouch
Ben Nadel at RIA Unleashed (Nov. 2010) with: David Crouch

Triggering $digest Phases In Related Directives In AngularJS

By
Published in Comments (11)

In a previous post, I took a look at using the $scope.$digest() method in AngularJS as a possible performance optimization, over $scope.$apply(), in situations where you know that your view-model changes are local. In the comments to that post, Xavier Boubert asked about applying this optimization in several related directives at the same time. I've never done this personally, but it was an interesting question so I figured I'd take a stab at an answer.

Run this demo in my JavaScript Demos project on GitHub.

The key to my approach is understanding what aspects of AngularJS actually rely on the $digest lifecycle. Because AngularJS is so magical, and presents such a clean separation of concerns, it's easy to forget that it's just JavaScript; and, that it's not entirely driven by dirty-data checks. All the method invocation, all the event propagation - that's just vanilla JavaScript. The parts of AngularJS that are primarily concerned with dirty-data are the $watch() handlers.

I say all this because it means that we can use the Pub/Sub (Publish and Subscribe) mechanism, provided by the $scope chain, without initiating a $digest. This will allow our directives to use event-based (ie, decoupled) communication while keeping our $digests localized.

In the following exploration, I have two directives that listen for mouse-events and output the X/Y coordinates of the mouse cursor. When the "Left" directives captures a mouse event, it consumes it locally and then announces an event on the $rootScope. The "Right" directive then listens for that event and consumes it as well. Both the Left and the Right directives keep the $digest phase local to the current $scope.

In addition to the Left and Right directives, I also have two "higher up" Controllers. These are there in order to demonstrate that their $watch() handlers are not invoked during this whole directive-communication lifecycle (best seen in the video above).

The great thing about the $apply() method, in AngularJS, is that it "just works." But, with a large and complex user interface, the cost of that simplicity can be a sacrifice in performance. The good news is, AngularJS is architected in such a way that there always seems to be an "Angular way" to optimize, if and when you need it.

Want to use code from this post? Check out the license.

Reader Comments

7 Comments

Thank you for your post and your Stab. I'm ok with you, for this problem we can workaround. But it means we can rewrite ALL of the ng-directive and other objects like $http. WoOt.

My second point with "digest by feature" is "How can I call the digest of an other feature inside a digest?" Actually it's not possible without asynchronous behavior. So, in complex UI, without blinks. (Try with D&D)

I don't understand why it does not shock anyone to have to call the refresh of the entire page, even for small projects.

But again, really thank you for your great help ;-)

15,983 Comments

@Xavier,

As of AngularJS 1.2, it's possible to safely invoke a $digest from within another $digest if you use the $evalAsync() method:

www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm

Essentially what this is does is, rather than invoking a new $digest immediately, it adds the callback to the end of a queue that gets checked at the end of the $digest iteration. So, you're callback will get picked up in the *current* digest... or, if the current $digest is already over, it falls-back to an asynchronous digest (via [essentially] a timeout call).

The latter part of that - the "fallback" to an asynchronous $digest - is what was added in v1.2.

THAT said, it may not be as bad as you think. If you're dealing with one directive communicating with another, chances are that you will always *know* whether or not a digest is in effect since you typically have to trigger them yourself inside a directive.

15,983 Comments

@Xavier,

I think maybe you are too worries about certain aspects of directives. Yes, there are times when you do not know for certain if a $digest is already in place; however, since it's the developers responsibility to let AngularJS know when to execute a digest (from within a Directive) I would say that in the large majority of cases, it will never be ambiguous as to whether or not a digest is already running.

Do you have a particular use-case you are fighting against? Or are you just trying to wrap your head around the dirty-data-checking approach?

7 Comments

As I said in my JSFiddle example, me and my team are actually creating a IDE (WYSIWYG) webservice (like Microsoft Blend or Dreamweaver for example). There are many features in single page like :
* "Edition area" that may contain components. It manages D&D and resizable for each components added
* A specific panel with properties of selected components
* And many other panels and menus like "Layers explorer", "List of components to insert in Edition area", toolboxes with "save", "copy", "paste", etc.

In this project, we have hugely directives, filters, watchers, etc.

One of my cases:
The user changes the "size" property of a selected component in the "Properties panel". When he presses keyboard keys, I want to update ONLY the component view in the edition area. If AngularJS calls $apply at this time, all of the page will be dirty-data-checked. I don't want that because it's too slow for my big interface.

At this step, I successfully remove many $apply calls. But it means that I can't use ng- directives, like ng-click to focus the "with" text input for example. It's my first problem.

My second problem: The "width" value is a ng-model wrapped on a JS object. When this object changes, I have a $watch wich is called. In this $watch I want to $digest the component view in "Edition area". Without setTimeout, it simply doesn't possible as we are talking about since the beginning of this post.

I hope that what I say is clear enough ^^

15,983 Comments

@Xavier,

Sounds like you are building a really complex app. I've been building a fairly large AngularJS app - but, at any one time, there aren't toooo many things on one page, they way there might be an IDE. It's certainly an interesting problem.

In general, I think you are right, though - the more specialized and optimized your app needs to become, the less you are going to use the out-of-the-box directives that AngularJS comes with.

Good luck! What an interesting conversation :D

7 Comments

Thanks Ben!

I'm thinking about forking project to make an other AngularJS optimized or to make a special module. But I'm afraid I do not have time for this.

15,983 Comments

@Xavier,

Ha ha, yeah, that sounds like it would be a huge undertaking. You'd probably be better off just writing a few custom directives. Of course, another thing to take into account is that non-directive things also cause full-scope $digests, like $timeout() and $q.

Now that I saw that, I wonder if $http causes an $apply as well.... it mus, otherwise AngularJS wouldn't know how you applied the response data.

Ok, so if you create some key custom directives, and build a custom $timeout, $q, and $http service, you should be ok.... he says jokingly as that clearly represents a LOT of work :)

7 Comments

Make new directives and $ functions are not very complicated to make.

The second problem is more complicated. Call 2 digests views without $apply and deffer to refresh 2 parts of view in single shot is my real problem.

My job is to create very large web platforms. For now, only ExtJS works like a charm with big projects

3 Comments

Hey

Thanks for sharing your interesting insights and the workarounds :)

AngularJS strengths for small apps are a weakness for big apps and you have to workaround with custom directives and an event bus. BTW you'd better use $rootScope.$emit and $rootScope.$on VS the expensive broadcast, as explained by Christoph here : http://stackoverflow.com/questions/11252780/whats-the-correct-way-to-communicate-between-controllers-in-angularjs/19498009#19498009

Sometimes you need to make an hybrid approach integrating non-angular stuff inside your angular app. For example for the IDE, maybe the "wysiwyg" part could be rendered without Angular and all the associated binding stuff. That's how the angularJS based games works (game rendering is not angular).

idea : If the render part is your bottleneck, then make a ReactJS component for this part only and connect it to your AngularJS interface. Best of both worlds :)

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel