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

Adds the ability to reregister a watcher thru the property restore #13524

Closed
wants to merge 2 commits into from
Closed
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
70 changes: 59 additions & 11 deletions src/ng/rootScope.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -80,6 +81,10 @@ function $RootScopeProvider() {
return TTL;
};

function nextWatchId() {
return ++watchId;
}

function createChildScopeClass(parent) {
function ChildScope() {
this.$$watchers = this.$$nextSibling =
Expand Down Expand Up @@ -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);
Expand All @@ -396,7 +403,8 @@ function $RootScopeProvider() {
last: initWatchVal,
get: get,
exp: prettyPrintExpression || watchExp,
eq: !!objectEquality
eq: !!objectEquality,
id: nextWatchId()
};

lastDirtyWatch = null;
Expand All @@ -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);
}
}
});
},

/**
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
});
}
};
});
},


Expand Down Expand Up @@ -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);
}

}];
}
132 changes: 132 additions & 0 deletions test/ng/rootScopeSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down