Skip to content

Commit c9ee324

Browse files
committed
fix($parse): one-time binding for literal expressions works as expected
Meaning the watcher is only removed when all the properties of the object, or all the elements of the array, are defined.
1 parent 6e0ea0c commit c9ee324

File tree

2 files changed

+88
-2
lines changed

2 files changed

+88
-2
lines changed

src/ng/parse.js

+30-2
Original file line numberDiff line numberDiff line change
@@ -1017,8 +1017,12 @@ function $ParseProvider() {
10171017
var parser = new Parser(lexer, $filter, $parseOptions);
10181018
parsedExpression = parser.parse(exp);
10191019

1020-
if (oneTime) parsedExpression.$$beWatched = oneTimeWatch;
1021-
if (parsedExpression.constant) parsedExpression.$$beWatched = constantWatch;
1020+
if (parsedExpression.constant) {
1021+
parsedExpression.$$beWatched = constantWatch;
1022+
} else if (oneTime) {
1023+
parsedExpression.$$beWatched = parsedExpression.literal ?
1024+
oneTimeLiteralWatch : oneTimeWatch;
1025+
}
10221026

10231027
if (cacheKey !== 'hasOwnProperty') {
10241028
// Only cache the value if it's not going to mess up the cache object
@@ -1054,6 +1058,30 @@ function $ParseProvider() {
10541058
}, objectEquality, deregisterNotifier);
10551059
}
10561060

1061+
function oneTimeLiteralWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
1062+
var unwatch;
1063+
return unwatch = scope.$watch(function oneTimeWatch(scope) {
1064+
return parsedExpression(scope);
1065+
}, function oneTimeListener(value, old, scope) {
1066+
if (isFunction(listener)) {
1067+
listener.call(this, value, old, scope);
1068+
}
1069+
if (!hasUndefined(value)) {
1070+
scope.$$postDigest(function () {
1071+
if(!hasUndefined(value)) unwatch();
1072+
});
1073+
}
1074+
}, objectEquality, deregisterNotifier);
1075+
1076+
function hasUndefined(value) {
1077+
var hasUndefined = false;
1078+
forEach(value, function (val) {
1079+
if (!isDefined(val)) hasUndefined = true;
1080+
});
1081+
return hasUndefined;
1082+
}
1083+
}
1084+
10571085
function constantWatch(scope, listener, objectEquality, deregisterNotifier, parsedExpression) {
10581086
var unwatch;
10591087
return unwatch = scope.$watch(function constantWatch(scope) {

test/ng/parseSpec.js

+58
Original file line numberDiff line numberDiff line change
@@ -1023,6 +1023,64 @@ describe('parser', function() {
10231023
expect($rootScope.$$watchers.length).toBe(1);
10241024
expect(log).toEqual('; man');
10251025
}));
1026+
1027+
describe('literal expressions', function () {
1028+
it('should only become stable when all the properties of an object have defined values', inject(function ($parse, $rootScope, log){
1029+
var fn = $parse('::{foo: foo, bar: bar}');
1030+
$rootScope.$watch(fn, function(value) { log(value); }, true);
1031+
1032+
expect(log.empty()).toEqual([]);
1033+
expect($rootScope.$$watchers.length).toBe(1);
1034+
1035+
$rootScope.$digest();
1036+
expect($rootScope.$$watchers.length).toBe(1);
1037+
expect(log.empty()).toEqual([{foo: undefined, bar: undefined}]);
1038+
1039+
$rootScope.foo = 'foo';
1040+
$rootScope.$digest();
1041+
expect($rootScope.$$watchers.length).toBe(1);
1042+
expect(log.empty()).toEqual([{foo: 'foo', bar: undefined}]);
1043+
1044+
$rootScope.foo = 'foobar';
1045+
$rootScope.bar = 'bar';
1046+
$rootScope.$digest();
1047+
expect($rootScope.$$watchers.length).toBe(0);
1048+
expect(log.empty()).toEqual([{foo: 'foobar', bar: 'bar'}]);
1049+
1050+
$rootScope.foo = 'baz';
1051+
$rootScope.$digest();
1052+
expect($rootScope.$$watchers.length).toBe(0);
1053+
expect(log.empty()).toEqual([]);
1054+
}));
1055+
1056+
it('should only become stable when all the elements of an array have defined values', inject(function ($parse, $rootScope, log){
1057+
var fn = $parse('::[foo,bar]');
1058+
$rootScope.$watch(fn, function(value) { log(value); }, true);
1059+
1060+
expect(log.empty()).toEqual([]);
1061+
expect($rootScope.$$watchers.length).toBe(1);
1062+
1063+
$rootScope.$digest();
1064+
expect($rootScope.$$watchers.length).toBe(1);
1065+
expect(log.empty()).toEqual([[undefined, undefined]]);
1066+
1067+
$rootScope.foo = 'foo';
1068+
$rootScope.$digest();
1069+
expect($rootScope.$$watchers.length).toBe(1);
1070+
expect(log.empty()).toEqual([['foo', undefined]]);
1071+
1072+
$rootScope.foo = 'foobar';
1073+
$rootScope.bar = 'bar';
1074+
$rootScope.$digest();
1075+
expect($rootScope.$$watchers.length).toBe(0);
1076+
expect(log.empty()).toEqual([['foobar', 'bar']]);
1077+
1078+
$rootScope.foo = 'baz';
1079+
$rootScope.$digest();
1080+
expect($rootScope.$$watchers.length).toBe(0);
1081+
expect(log.empty()).toEqual([]);
1082+
}));
1083+
});
10261084
});
10271085

10281086
describe('locals', function() {

0 commit comments

Comments
 (0)