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

feat($rootScope): Implement stopPropatagion for $broadcast events #15877

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -1151,8 +1151,8 @@ function $RootScopeProvider() {
* - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
* event propagates through the scope hierarchy, this property is set to null.
* - `name` - `{string}`: name of the event.
* - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
* further event propagation (available only for events that were `$emit`-ed).
* - `stopPropagation` - `{function}`: calling `stopPropagation` function will cancel
* further event propagation.
* - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
* to true.
* - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
Expand Down Expand Up @@ -1272,7 +1272,9 @@ function $RootScopeProvider() {
* The event life cycle starts at the scope on which `$broadcast` was called. All
* {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
* notified. Afterwards, the event propagates to all direct and indirect scopes of the current
* scope and calls all registered listeners along the way. The event cannot be canceled.
* scope and calls all registered listeners along the way. If a scope requests the event stop propagation
* then it will not be propagated to any children of that scope, but will continue to propagate to siblings of the
* that scope and children of those siblings unless each sibling independently stops the event.
*
* Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
Expand All @@ -1284,10 +1286,14 @@ function $RootScopeProvider() {
$broadcast: function(name, args) {
var target = this,
current = target,
stopPropagation = false,
next = target,
event = {
name: name,
targetScope: target,
stopPropagation: function() {
stopPropagation = true;
},
preventDefault: function() {
event.defaultPrevented = true;
},
Expand All @@ -1301,6 +1307,7 @@ function $RootScopeProvider() {

//down while you can, then up and next sibling or up and next sibling until back at root
while ((current = next)) {
stopPropagation = false;
event.currentScope = current;
listeners = current.$$listeners[name] || [];
for (i = 0, length = listeners.length; i < length; i++) {
Expand All @@ -1322,8 +1329,8 @@ function $RootScopeProvider() {
// Insanity Warning: scope depth-first traversal
// yes, this code is a bit crazy, but it works and we have tests to prove it!
// this piece should be kept in sync with the traversal in $digest
// (though it differs due to having the extra check for $$listenerCount)
if (!(next = ((current.$$listenerCount[name] && current.$$childHead) ||
// (though it differs due to having the extra check for $$listenerCount and stopPropagation)
if (!(next = ((current.$$listenerCount[name] && !stopPropagation && current.$$childHead) ||
(current !== target && current.$$nextSibling)))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

PR #12672 has an additional check here, why not this PR?

Copy link
Author

Choose a reason for hiding this comment

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

Actually it doesn't, it has an assignment here, since it's not part of the condition and sin e the condition is already confusing enough i put mine on line 1310

while (current !== target && !(next = current.$$nextSibling)) {
current = current.$parent;
Expand Down
32 changes: 32 additions & 0 deletions test/ng/rootScopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2326,6 +2326,38 @@ describe('Scope', function() {
expect(result.name).toBe('some');
expect(result.targetScope).toBe(child1);
});


it('should not descend past scopes that stop propagation', inject(function($rootScope) {
child1.$on('myEvent', function(event) {
event.stopPropagation();
});

$rootScope.$broadcast('myEvent');
expect(log).toBe('0>1>2>21>211>22>23>3>');
}));

it('should continue to pass through and past sibling scopes to one that stopped propagation',
inject(function($rootScope) {
grandChild21.$on('myEvent', function(event) {
event.stopPropagation();
});

$rootScope.$broadcast('myEvent');
expect(log).toBe('0>1>11>2>21>22>23>3>');
}));

it('should allow multiple separate subtrees to stop propagation independently', inject(function($rootScope) {
var stopPropagation = function(event) {
event.stopPropagation();
};
child1.$on('myEvent', stopPropagation);
grandChild21.$on('myEvent', stopPropagation);
grandChild22.$on('myEvent', stopPropagation);

$rootScope.$broadcast('myEvent');
expect(log).toBe('0>1>2>21>22>23>3>');
}));
});


Expand Down