From 5ce782e4a80149dbcb684693d3a27339fd07d3ad Mon Sep 17 00:00:00 2001 From: Shahar Talmi Date: Wed, 7 Jan 2015 02:12:53 +0200 Subject: [PATCH] feat($rootScope): allow suspending and resuming watchers on scope This can be very helpful for external modules that help making the digest loop faster by ignoring some of the watchers under some circumstance. example: https://github.com/shahata/angular-viewport-watch --- src/ng/rootScope.js | 34 ++++++++++++++++++++++++++++++++-- test/ng/rootScopeSpec.js | 30 ++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index be150faf1c15..50e0b09a5782 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -133,6 +133,7 @@ function $RootScopeProvider() { this.$$childHead = this.$$childTail = null; this.$root = this; this.$$destroyed = false; + this.$$suspended = false; this.$$listeners = {}; this.$$listenerCount = {}; this.$$isolateBindings = null; @@ -751,7 +752,7 @@ function $RootScopeProvider() { traverseScopesLoop: do { // "traverse the scopes" loop - if ((watchers = current.$$watchers)) { + if ((watchers = !current.$$suspended && current.$$watchers)) { // process our watches length = watchers.length; while (length--) { @@ -794,7 +795,7 @@ 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 $broadcast - if (!(next = (current.$$childHead || + if (!(next = (!current.$$suspended && current.$$childHead || (current !== target && current.$$nextSibling)))) { while (current !== target && !(next = current.$$nextSibling)) { current = current.$parent; @@ -825,6 +826,35 @@ function $RootScopeProvider() { } }, + /** + * @ngdoc method + * @name $rootScope.Scope#$suspend + * @kind function + * + * @description + * Suspend watchers of this scope subtree so that they will not be invoked during digest. + * + * This can be used to optimize your application when you know that running those watchers + * is redundant. + */ + $suspend: function() { + this.$$suspended = true; + }, + + /** + * @ngdoc method + * @name $rootScope.Scope#$resume + * @kind function + * + * @description + * Resume watchers of this scope subtree in case it was suspended. + * + * It is recommended to digest on this scope after it is resumed to catch any modifications + * that might have happened while it was suspended. + */ + $resume: function() { + this.$$suspended = false; + }, /** * @ngdoc event diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 89d30820ff24..094e03d34214 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -775,6 +775,36 @@ describe('Scope', function() { }); + describe('$suspend/$resume', function() { + it('should suspend watchers on scope', inject(function($rootScope) { + var watchSpy = jasmine.createSpy('watchSpy'); + $rootScope.$watch(watchSpy); + $rootScope.$suspend(); + $rootScope.$digest(); + expect(watchSpy).not.toHaveBeenCalled(); + })); + + it('should resume watchers on scope', inject(function($rootScope) { + var watchSpy = jasmine.createSpy('watchSpy'); + $rootScope.$watch(watchSpy); + $rootScope.$suspend(); + $rootScope.$resume(); + $rootScope.$digest(); + expect(watchSpy).toHaveBeenCalled(); + })); + + it('should suspend watchers on child scope', inject(function($rootScope) { + var watchSpy = jasmine.createSpy('watchSpy'); + var scope = $rootScope.$new(true); + scope.$watch(watchSpy); + $rootScope.$suspend(); + $rootScope.$digest(); + expect(watchSpy).not.toHaveBeenCalled(); + })); + + }); + + describe('optimizations', function() { function setupWatches(scope, log) {