Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

perf(Scope): limit propagation of $broadcast #5371

Closed
wants to merge 1 commit into from

Conversation

kseamon
Copy link
Contributor

@kseamon kseamon commented Dec 11, 2013

perf(Scope): limit propagation of $broadcast to scopes that have registered the event

Update $on and $destroy to maintain a count of event keys registered for each scope and its children.
$broadcast will not descend past a node that has a count of 0/undefined for the $broadcasted event key.

Resolves #5341

@ghost ghost assigned jeffbcross Dec 11, 2013
@cburgdorf
Copy link
Contributor

I really like the fact that awareness of the issue seems to grow among angular folks. And I do think this is a great addition to make $broadcast faster among most situations. However, I still would treat $rootScope.$broadcast() as an anti pattern and rather use $rootScope.$emit then. If one is tempted to use $rootScope.$broadcast then it just shows that the developer really wants to dispatch a notification to everyone a so called application wide event. And if one wants to notify everyone why walk through $scopes at all? Just $emit on the $rootScope and declare the event as being application wide.

Users should then bind through $rootScope.$on or even better through $scope.$onRootScope as described here and here.

I would still vote for angular to provide such an $scope.$onRootScope method directly in core. Maybe I would even raise an exception if one writes $rootScope.$broadcast.

Because the problem is, let's say you have a directive (deeply nested in scopes) which is used plenty of times on your site (e.g. in a repeater) which binds via $scope.$on to an event which was dispatched via $rootScope.$broadcast then your patch won't help for such situations. Angular will still walk down the entire scope tree to reach all those scopes which do bind to that event. Why? What for? It's a global event, just declare it as such and use it as such :)

Just my two cents ;-)

@kseamon
Copy link
Contributor Author

kseamon commented Dec 18, 2013

To be fair, it will only walk down the branches of the tree needed to get to your deeply nestled scope, but will not pass through the sibling branches on the way.

@@ -998,8 +1020,7 @@ function $RootScopeProvider(){
listeners, i, length;

//down while you can, then up and next sibling or up and next sibling until back at root
do {
current = next;
while ((current = next)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which change do-while to while? it doesn't seem to make a difference and it causes the code to go out of sync with the traversal code in digest

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It struck me as redundant and weird:

do {
current = next;
...
} while ((current = next));

is identical to (or perhaps a very tiny bit slower than) the simpler:

while ((current = next)) {
...
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And for that matter, they were already out of sync in this respect. The similar loop in $digest is:

do {
// current = next is not here.
} while ((current = next))

@IgorMinar
Copy link
Contributor

otherwise this looks good.

I do like what @cburgdorf suggested and I triaged #4574 for 1.3

cburgdorf added a commit to cburgdorf/angular.js that referenced this pull request Dec 21, 2013
This adds an $onRootScope method to the
scope type. This allows developers to use
$rootScope.$emit + $scope.$onRootScope as
a fast eventBus which doesn't use scope
bubbleing.

Fixes angular#4574, Relates to angular#5371
}
current.$$listenerCount[name]++;
} while ((current = current.$parent));

return function() {
namedListeners[indexOf(namedListeners, listener)] = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to decrement the counters here as well

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@IgorMinar
Copy link
Contributor

it would be better to refactor this by creating a single function that adjusts the counters and then use this fn to increment and decrement counters in all 3 places instead of repeating the same code everywhere

spy = jasmine.createSpy('listener'),
firstSecond = first.$new();

firstSecond.$on(EVENT, spy);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please register two listeners for this event so that we test that we decrement the counters by the right amount (not just by one)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

…stered the event

Update $on and $destroy to maintain a count of event keys registered for each scope and its children.
$broadcast will not descend past a node that has a count of 0/undefined for the $broadcasted event key.

Resolves angular#5341
@IgorMinar IgorMinar closed this in 80e7a45 Dec 28, 2013
@bfricka
Copy link

bfricka commented Jan 8, 2014

@cburgdorf I am perhaps misunderstanding. When you say:

If one is tempted to use $rootScope.$broadcast then it just shows that the developer really wants to dispatch a notification to everyone a so called application wide event.

I don't see this as being true at all. The documentation clearly distinguishes $broadcast and $emit, and there are clear non-$rootScope use-cases for $broadcast. For example we use it extensively within directives to broadcast event information to sub-directives that we want to occur outside the $digest cycle (for performance). While we could also send this information through the directive's controller chain, it's cleaner to keep sub-directives "dumb".

I realize that this landed and the change is great. I'm just adding this to make it clear that $broadcast is a fundamentally sound concept IMO.

@cburgdorf
Copy link
Contributor

It's indeed a misunderstanding. What I am saying is $scope.broadcast() is fine but $rootScope.$broadcast() is not. Can you come up with any example where you would use $rootScope.$broadcast() which shouldn't be replaced with $rootScope.$emit()?

@bfricka
Copy link

bfricka commented Jan 9, 2014

Cool, thanks for clarifying.

jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
…eners for the event

Update $on and $destroy to maintain a count of event keys registered for each scope and its children.
$broadcast will not descend past a node that has a count of 0/undefined for the $broadcasted event key.

Closes angular#5341
Closes angular#5371
jamesdaily pushed a commit to jamesdaily/angular.js that referenced this pull request Jan 27, 2014
…eners for the event

Update $on and $destroy to maintain a count of event keys registered for each scope and its children.
$broadcast will not descend past a node that has a count of 0/undefined for the $broadcasted event key.

Closes angular#5341
Closes angular#5371
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize away broadcasts (and maybe emits) with no listeners
5 participants