From c024f28217cf8eedd695dd4b933ecf2ba4243c15 Mon Sep 17 00:00:00 2001 From: rodyhaddad Date: Wed, 4 Jun 2014 17:15:18 -0700 Subject: [PATCH] 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. Closes #8209 --- src/ng/parse.js | 32 ++++++++++++++++++++++-- test/ng/parseSpec.js | 58 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/src/ng/parse.js b/src/ng/parse.js index d1766b9a6425..11cf501d9f83 100644 --- a/src/ng/parse.js +++ b/src/ng/parse.js @@ -1012,8 +1012,12 @@ function $ParseProvider() { var parser = new Parser(lexer, $filter, $parseOptions); parsedExpression = parser.parse(exp); - if (parsedExpression.constant) parsedExpression.$$watchDelegate = constantWatch; - else if (oneTime) parsedExpression.$$watchDelegate = oneTimeWatch; + if (parsedExpression.constant) { + parsedExpression.$$watchDelegate = constantWatch; + } else if (oneTime) { + parsedExpression.$$watchDelegate = parsedExpression.literal ? + oneTimeLiteralWatch : oneTimeWatch; + } if (cacheKey !== 'hasOwnProperty') { // Only cache the value if it's not going to mess up the cache object @@ -1050,6 +1054,30 @@ function $ParseProvider() { }, objectEquality); } + function oneTimeLiteralWatch(scope, listener, objectEquality, parsedExpression) { + var unwatch; + return unwatch = scope.$watch(function oneTimeWatch(scope) { + return parsedExpression(scope); + }, function oneTimeListener(value, old, scope) { + if (isFunction(listener)) { + listener.call(this, value, old, scope); + } + if (isAllDefined(value)) { + scope.$$postDigest(function () { + if(isAllDefined(value)) unwatch(); + }); + } + }, objectEquality); + + function isAllDefined(value) { + var allDefined = true; + forEach(value, function (val) { + if (!isDefined(val)) allDefined = false; + }); + return allDefined; + } + } + function constantWatch(scope, listener, objectEquality, parsedExpression) { var unwatch; return unwatch = scope.$watch(function constantWatch(scope) { diff --git a/test/ng/parseSpec.js b/test/ng/parseSpec.js index dd2aa555a1a1..27994bd0881c 100644 --- a/test/ng/parseSpec.js +++ b/test/ng/parseSpec.js @@ -1174,6 +1174,64 @@ describe('parser', function() { $rootScope.$digest(); expect(fn()).toEqual(null); })); + + describe('literal expressions', function () { + it('should only become stable when all the properties of an object have defined values', inject(function ($parse, $rootScope, log){ + var fn = $parse('::{foo: foo, bar: bar}'); + $rootScope.$watch(fn, function(value) { log(value); }, true); + + expect(log.empty()).toEqual([]); + expect($rootScope.$$watchers.length).toBe(1); + + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(1); + expect(log.empty()).toEqual([{foo: undefined, bar: undefined}]); + + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(1); + expect(log.empty()).toEqual([{foo: 'foo', bar: undefined}]); + + $rootScope.foo = 'foobar'; + $rootScope.bar = 'bar'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(0); + expect(log.empty()).toEqual([{foo: 'foobar', bar: 'bar'}]); + + $rootScope.foo = 'baz'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(0); + expect(log.empty()).toEqual([]); + })); + + it('should only become stable when all the elements of an array have defined values', inject(function ($parse, $rootScope, log){ + var fn = $parse('::[foo,bar]'); + $rootScope.$watch(fn, function(value) { log(value); }, true); + + expect(log.empty()).toEqual([]); + expect($rootScope.$$watchers.length).toBe(1); + + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(1); + expect(log.empty()).toEqual([[undefined, undefined]]); + + $rootScope.foo = 'foo'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(1); + expect(log.empty()).toEqual([['foo', undefined]]); + + $rootScope.foo = 'foobar'; + $rootScope.bar = 'bar'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(0); + expect(log.empty()).toEqual([['foobar', 'bar']]); + + $rootScope.foo = 'baz'; + $rootScope.$digest(); + expect($rootScope.$$watchers.length).toBe(0); + expect(log.empty()).toEqual([]); + })); + }); }); describe('locals', function() {