Using $rootScope.$emit() As A Performance Optimization In AngularJS
Yesterday, I looked at how to create a simple modal window system in AngularJS. In that exploration, I used the $rootScope to bridge the gap between the modal Service and the modal directive. In the past, I've used the same approach to bridge the gap between a global uploader service and its Plupload directive. As it turns out, the scope tree acts as a wonderful pub/sub (Publish and Subscribe) mechanism; and, in certain edge-cases we can optimize our events by using the $rootScope.$emit() method.
When it comes to scope tree events, there are two event broadcasting methods: scope.$broadcast() and scope.$emit(). The $broadcast() method will send events down through the scope tree's descendants. The $emit() method will send events up through the scope tree's ancestors. When you bind to an event using the scope.$on() method, your handler will be invoked regardless of how the original event was triggered (ie, broadcast vs emit).
In most cases, the $broadcast() method makes the most sense as you cannot be sure which components in the application will want to know about your event. But, in certain edge-cases, you need to use events to create communication between two very specific components (such as a modal window Service and a modal window Directive). In these cases, using $rootScope.$emit() can provide a small performance optimization over $rootScope.$broadcast().
NOTE: I picked this idea up from Nicolas Bevacqua's excellent article on the AngularJS internals.
The optimization is a byproduct of the scope tree structure. Since the $rootScope has no parent (ancestors), an event, $emit()'d event on the $rootScope, has no where to go. As such, it triggers the $rootScope-bound handlers and then ends its lifecycle.
If a service needs to communicate with a directive, they can both use the $rootScope as their pub/sub beacon and the overhead of the event is entirely eliminated. To see what this might look like, I've tried to boil it down to a very simple demo in which a directive needs to listen for events triggered by the service layer:
As you can see, the service object uses $rootScope.$emit() to announce interval events and the directive uses $rootScope.$on() to listen for those events. The directive can't use scope.$on(), in this case, because the emitted event never comes down through the scope tree.
The obvious downside to this is that the $rootScope-bound event listener will not automatically unbind itself when the directive scope is destroyed. Therefore, if your directive is ever destroyed, you have to be sure to explicitly unbind the event handler; otherwise, it will just stick around forever, causing a memory leak at best and unexpected behavior at worst.
Now, as it turns out, the $broadcast() algorithm, in AngularJS, is heavily optimized; AngularJS will only traverse areas of the scope tree that are known to have bound-listeners for a certain event type. So, while the $rootScope.$emit() approach is faster, it may also be unnecessary in recent releases of AngularJS. And, considering the complexity of having to manually unbind events, the $rootScope optimization may be more trouble than it's worth. That said, it's definitely an interesting approach - one well worth pondering.
Want to use code from this post? Check out the license.
It's nice to see a great example using $emit because for a while it felt just like a leftover from the early days of Angular. Thanks for putting that together Ben!