diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 3658d376e0a1..15e554d34455 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -72,6 +72,7 @@ function $RootScopeProvider() { var $rootScopeMinErr = minErr('$rootScope'); var lastDirtyWatch = null; var applyAsyncId = null; + var watchId = 0; this.digestTtl = function(value) { if (arguments.length) { @@ -80,6 +81,10 @@ function $RootScopeProvider() { return TTL; }; + function nextWatchId() { + return ++watchId; + } + function createChildScopeClass(parent) { function ChildScope() { this.$$watchers = this.$$nextSibling = @@ -382,6 +387,8 @@ function $RootScopeProvider() { * @param {boolean=} [objectEquality=false] Compare for object equality using {@link angular.equals} instead of * comparing for reference equality. * @returns {function()} Returns a deregistration function for this listener. + * The property `restore` of the returning function is a function that allows restoring the + * watcher once it was deregistered. */ $watch: function(watchExp, listener, objectEquality, prettyPrintExpression) { var get = $parse(watchExp); @@ -396,7 +403,8 @@ function $RootScopeProvider() { last: initWatchVal, get: get, exp: prettyPrintExpression || watchExp, - eq: !!objectEquality + eq: !!objectEquality, + id: nextWatchId() }; lastDirtyWatch = null; @@ -413,12 +421,23 @@ function $RootScopeProvider() { array.unshift(watcher); incrementWatchersCount(this, 1); - return function deregisterWatch() { - if (arrayRemove(array, watcher) >= 0) { + return extend(function deregisterWatch() { + var index = binarySearch(array, watcher.id); + if (index >= 0) { + array.splice(index, 1); + lastDirtyWatch = null; incrementWatchersCount(scope, -1); } - lastDirtyWatch = null; - }; + }, { + restore: function() { + var index = binarySearch(array, watcher.id); + if (index < 0) { + array.splice(-index - 1, 0, watcher); + lastDirtyWatch = null; + incrementWatchersCount(scope, 1); + } + } + }); }, /** @@ -460,9 +479,13 @@ function $RootScopeProvider() { self.$evalAsync(function() { if (shouldCall) listener(newValues, newValues, self); }); - return function deregisterWatchGroup() { + return extend(function deregisterWatchGroup() { shouldCall = false; - }; + }, { + restore: function() { + shouldCall = true; + } + }); } if (watchExpressions.length === 1) { @@ -497,11 +520,17 @@ function $RootScopeProvider() { } } - return function deregisterWatchGroup() { - while (deregisterFns.length) { - deregisterFns.shift()(); + return extend(function deregisterWatchGroup() { + forEach(deregisterFns, function(deregisterFn) { + deregisterFn(); + }); + }, { + restore: function() { + forEach(deregisterFns, function(deregisterFn) { + deregisterFn.restore(); + }); } - }; + }); }, @@ -1361,5 +1390,24 @@ function $RootScopeProvider() { }); } } + + // Array is ordered in descending order by id + function binarySearch(array, id) { + var low = 0; + var mid; + var high = array.length - 1; + var value; + while (low <= high) { + // jshint bitwise: false + mid = (low + high) >>> 1; + // jshint bitwise: true + value = array[mid].id; + if (value > id) low = mid + 1; + else if (value < id) high = mid - 1; + else return mid; + } + return -(low + 1); + } + }]; } diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index a9a1ae120494..f0c4544b70c3 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -604,6 +604,138 @@ describe('Scope', function() { }); + describe('watch reregistration', function() { + it('should be possible to reregister a watcher', inject(function($rootScope, log) { + var w1 = $rootScope.$watch(log.fn('watch1'), noop); + var w2 = $rootScope.$watch(log.fn('watch2'), noop); + var w3 = $rootScope.$watch(log.fn('watch3'), noop); + + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3', 'watch1', 'watch2', 'watch3']); + log.reset(); + + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w1(); + $rootScope.$digest(); + expect(log).toEqual(['watch2', 'watch3']); + log.reset(); + + w1.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w2(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch3']); + log.reset(); + + w2.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w3(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2']); + log.reset(); + + w3.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + })); + + + it('should not add multiple times the same watcher when calling multiple times `restore`', inject(function($rootScope, log) { + var w1 = $rootScope.$watch(log.fn('watch1'), noop); + var w2 = $rootScope.$watch(log.fn('watch2'), noop); + var w3 = $rootScope.$watch(log.fn('watch3'), noop); + + $rootScope.$digest(); + log.reset(); + + w1(); + $rootScope.$digest(); + log.reset(); + w1.restore(); + w1.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w1(); + $rootScope.$digest(); + log.reset(); + w1.restore(); + w1(); + w1.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + })); + + it('should be possible to reregister a watcher from watchGroup', inject(function($rootScope, log) { + var w = $rootScope.$watchGroup([log.fn('watch1'), log.fn('watch2'), log.fn('watch3')], noop); + + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3', 'watch1', 'watch2', 'watch3']); + log.reset(); + + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w(); + $rootScope.$digest(); + expect(log).toEqual([]); + log.reset(); + + w.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + + w.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch1', 'watch2', 'watch3']); + log.reset(); + })); + + it('should be possible to reregister a watcher from watchCollection', inject(function($rootScope, log) { + $rootScope.obj = [0, 1, 2]; + var w = $rootScope.$watchCollection('obj', log.fn('watch!')); + + $rootScope.$digest(); + expect(log).toEqual(['watch!']); + log.reset(); + + $rootScope.$digest(); + expect(log).toEqual([]); + log.reset(); + + $rootScope.obj.push(3); + $rootScope.$digest(); + expect(log).toEqual(['watch!']); + log.reset(); + + w(); + $rootScope.obj.push(4); + $rootScope.$digest(); + expect(log).toEqual([]); + log.reset(); + + w.restore(); + $rootScope.$digest(); + expect(log).toEqual(['watch!']); + log.reset(); + })); + }); + + describe('$watchCollection', function() { var log, $rootScope, deregister;