$on fall under the common “publish/subscribe” design pattern, or can do, in which you’d publish an event and subscribe/unsubscribe to it somewhere else. The Angular event system is brilliant, it makes things flawless and easy to do (as you’d expect!) but the concept behind it isn’t so simple to master and you can often be left wondering why things don’t work as you thought they might.
Table of contents
For those who are new to Angular and haven’t used or seen
$on, let’s clarify what they do before we look at
$rootScope event and scope relationships and how to utilise the event system correctly - as well as understand what’s really going on.
$scope.$emit up, $scope.$broadcast down
$scope.$emit will fire an event up the
$scope.$broadcast will fire an event down the
$scope.$on is how we listen for these events. A quick example:
The key thing to remember when using
$scope to fire your events, is that they will communicate only with immediate parent or child scopes only! Scopes aren’t always child and parent. We might have sibling scopes. Using
$scope to fire an event will miss out sibling scopes, and just carry on up! They do not go sideways!
The simplest way to emulate parent and child scopes are to use Controllers. Each Controller creates new
$scope, which Angular neatly outputs an
ng-scope class on newly scoped elements for us:
We could fire an event down from
If we wanted to communicate upwards, from
ParentCtrl, you guessed it, we can use
To demonstrate how
$scope works when firing the events, here’s a simple hierarchy:
SiblingOneCtrl would never know it happened. This can be an annoyance, but a (slightly hacky-feely) remedy can be done:
What this does is jump up to
ParentCtrl and then fire the
$broadcast from there.
If things weren’t complicated enough, let’s throw in
$rootScope as well.
$rootScope is the parent of all scopes, which makes every newly created
$scope a descendent! I mentioned above about how
$scope is limited to direct scopes,
$rootScope is how we could communicate across scopes with ease. Doing this will fit certain scenarios better than others. It’s not as simple as up or down the scopes though, unfortunately…
$rootScope.$emit versus $rootScope.$broadcast
$rootScope Object has the identical
$on methods, but they work slightly differently to how
$scope implements them. As
$rootScope has no
$parent, using an
$emit would be pointless, right? Nope, instead,
$rootScope.$emit will fire an event for all
$rootScope.$on listeners only. The interesting part is that
$rootScope.$broadcast will notify all
$rootScope.$on as well as
$scope.$on listeners, subtle but very important difference if you want to avoid issues in your application.
Let’s take an even deeper hierarchy:
The above has 3 lexical scopes (where parent scopes are accessible in the current scope, kind of hurts your brain to think about it in terms of DOM scoping, but the concepts are there) and 4 Angular scopes,
ChildCtrl. Two sibling scopes.
ChildCtrl would result in
ParentCtrl only being notified, as the event doesn’t hit sibling scopes only direct ancestors (completely ignoring
SiblingOneCtrl). If we used
$rootScope, however, then we can target
$rootScope listeners as well.
Unsubscribing from events
As part of the event system, you can unsubscribe from events at any time with the
$on listener. Unlike other libraries, there is no
$off method. The Angular docs aren’t particularly clear on how to “unsubscribe”, the docs say that
$on “Returns a deregistration function for this listener.”. We can assume by that they mean a
closure which allows us to unsubscribe.
Inside the source code of
v1.3.0-beta.11, we can locate the
$on method and confirm suspicions of a closure:
We can subscribe and unsubscribe very easily:
$rootScope.$on, we need to unbind those listeners each time the
$scope is destroyed.
$scope.$on listeners are automatically unbound, but we’ll need to call the above closure manually on the
If you choose to use
$emit, one of your other
$scope listeners can cancel it, so prevent it bubbling further. Using
$broadcast has the opposite effect in which it cannot be cancelled!
Cancelling an event which was sent via
$emit looks like this:
Every Angular Object has several properties, we can dig into them and observe what’s happening “under the hood”. We can take a look at
$rootScope.$$listeners to observe the listeners lifecycle. We can also unsubscribe from events that way as well by using this (but I wouldn’t encourage it):
Generally if I’m working on a particular Factory, I’ll communicate to other Directives or Controllers or even Factories using a specific namespace for cleaner pub/subs, which keeps things consistent and avoid naming conflicts.
If I were building an email application with an Inbox, we might use an
inbox namespace for that specific section. This is easily integrated with a few simple examples:
Dig through the docs for anything further! :)