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() {