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

Commit 51ca67f

Browse files
feat($rootScope): Implement stopPropatagion for $broadcast events
$scope.$broadcast dispatches an event to all child scopes by traversing the scope tree in a depth-first manner. This change allows any scope to prevent it's children from receiving that event by calling stopPropagation on the event object. Other listeners on the scope which called stopPropagation will continue to receive the event, as will siblings of that scope and any children of those siblings.
1 parent 617b361 commit 51ca67f

File tree

2 files changed

+44
-5
lines changed

2 files changed

+44
-5
lines changed

src/ng/rootScope.js

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,8 +1151,8 @@ function $RootScopeProvider() {
11511151
* - `currentScope` - `{Scope}`: the scope that is currently handling the event. Once the
11521152
* event propagates through the scope hierarchy, this property is set to null.
11531153
* - `name` - `{string}`: name of the event.
1154-
* - `stopPropagation` - `{function=}`: calling `stopPropagation` function will cancel
1155-
* further event propagation (available only for events that were `$emit`-ed).
1154+
* - `stopPropagation` - `{function}`: calling `stopPropagation` function will cancel
1155+
* further event propagation.
11561156
* - `preventDefault` - `{function}`: calling `preventDefault` sets `defaultPrevented` flag
11571157
* to true.
11581158
* - `defaultPrevented` - `{boolean}`: true if `preventDefault` was called.
@@ -1272,7 +1272,9 @@ function $RootScopeProvider() {
12721272
* The event life cycle starts at the scope on which `$broadcast` was called. All
12731273
* {@link ng.$rootScope.Scope#$on listeners} listening for `name` event on this scope get
12741274
* notified. Afterwards, the event propagates to all direct and indirect scopes of the current
1275-
* scope and calls all registered listeners along the way. The event cannot be canceled.
1275+
* scope and calls all registered listeners along the way. If a scope requests the event stop propagation
1276+
* then it will not be propagated to any children of that scope, but will continue to propagate to siblings of the
1277+
* that scope and children of those siblings unless each sibling independently stops the event.
12761278
*
12771279
* Any exception emitted from the {@link ng.$rootScope.Scope#$on listeners} will be passed
12781280
* onto the {@link ng.$exceptionHandler $exceptionHandler} service.
@@ -1284,10 +1286,14 @@ function $RootScopeProvider() {
12841286
$broadcast: function(name, args) {
12851287
var target = this,
12861288
current = target,
1289+
stopPropagation = false,
12871290
next = target,
12881291
event = {
12891292
name: name,
12901293
targetScope: target,
1294+
stopPropagation: function() {
1295+
stopPropagation = true;
1296+
},
12911297
preventDefault: function() {
12921298
event.defaultPrevented = true;
12931299
},
@@ -1301,6 +1307,7 @@ function $RootScopeProvider() {
13011307

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

test/ng/rootScopeSpec.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2326,6 +2326,38 @@ describe('Scope', function() {
23262326
expect(result.name).toBe('some');
23272327
expect(result.targetScope).toBe(child1);
23282328
});
2329+
2330+
2331+
it('should not descend past scopes that stop propagation', inject(function($rootScope) {
2332+
child1.$on('myEvent', function(event) {
2333+
event.stopPropagation();
2334+
});
2335+
2336+
$rootScope.$broadcast('myEvent');
2337+
expect(log).toBe('0>1>2>21>211>22>23>3>');
2338+
}));
2339+
2340+
it('should continue to pass through and past sibling scopes to one that stopped propagation',
2341+
inject(function($rootScope) {
2342+
grandChild21.$on('myEvent', function(event) {
2343+
event.stopPropagation();
2344+
});
2345+
2346+
$rootScope.$broadcast('myEvent');
2347+
expect(log).toBe('0>1>11>2>21>22>23>3>');
2348+
}));
2349+
2350+
it('should allow multiple separate subtrees to stop propagation independently', inject(function($rootScope) {
2351+
var stopPropagation = function(event) {
2352+
event.stopPropagation();
2353+
};
2354+
child1.$on('myEvent', stopPropagation);
2355+
grandChild21.$on('myEvent', stopPropagation);
2356+
grandChild22.$on('myEvent', stopPropagation);
2357+
2358+
$rootScope.$broadcast('myEvent');
2359+
expect(log).toBe('0>1>2>21>22>23>3>');
2360+
}));
23292361
});
23302362

23312363

0 commit comments

Comments
 (0)