Understanding Angular’s $scope and $rootScope event system $emit, $broadcast and $on

Reference from: https://toddmotto.com/all-about-angulars-emit-broadcast-on-publish-subscribing/

Angular’s $emit, $broadcast and $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.

For those who are new to Angular and haven’t used or seen $emit, $broadcastor $on, let’s clarify what they do before we look at $scope and $rootScopeevent 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

Using $scope.$emit will fire an event up the $scope. Using $scope.$broadcastwill fire an event down the $scope. Using $scope.$on is how we listen for these events. A quick example:

$scope.($emit/$broadcast)

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 ParentCtrl to SiblingOneCtrl using$broadcast:

If we wanted to communicate upwards, from SiblingOneCtrl to ParentCtrl, you guessed it, we can use $emit.

To demonstrate how $scope works when firing the events, here’s a simple hierarchy:

If SiblingTwoCtrl fired $scope.$broadcast, then SiblingOneCtrl would neverknow 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.

$rootScope.($emit/$broadcast)

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 $scopea 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

The $rootScope Object has the identical $emit, $broadcast, $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.$onas well as $scope.$on listeners, subtle but very important difference if you want to avoid issues in your application.

$rootScope examples

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, ParentCtrl, SiblingOneCtrl,SiblingTwoCtrl and ChildCtrl. Two sibling scopes.

Using $scope.$emit inside ChildCtrl would result in SiblingTwoCtrl andParentCtrl only being notified, as the event doesn’t hit sibling scopes only directancestors (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 aclosure 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 $destroy

When using $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 $destroy event:

Cancelling events

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 itcannot be cancelled!

Cancelling an event which was sent via $emit looks like this:

$rootScope.$$listeners

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):

Event namespacing

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 inboxnamespace for that specific section. This is easily integrated with a few simple examples:

Further reading

Dig through the docs for anything further! 🙂

216 total views, 1 views today

Trả lời