From 43072e3812e32b89b97ad03144577cba50d4b776 Mon Sep 17 00:00:00 2001 From: Tobias Bosch Date: Thu, 12 Dec 2013 15:36:16 -0800 Subject: [PATCH] fix($compile): Allow literals in isolate scope references When a component uses an isolate scope reference and the the component is used with an object literal a new object is created on every evaluation. Therefore the compiler needs to compare the values of the parent and the isolate scope using object equality and not object reference equality. Fixes #5296. --- src/ng/compile.js | 14 +++++++---- test/ng/compileSpec.js | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 5 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 7d0bb008db21..a6bcc3e754ae 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1400,7 +1400,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { optional = (match[2] == '?'), mode = match[1], // @, =, or & lastValue, - parentGet, parentSet; + parentGet, parentSet, compare; isolateScope.$$isolateBindings[scopeName] = mode + attrName; @@ -1423,6 +1423,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { return; } parentGet = $parse(attrs[attrName]); + if (parentGet.literal) { + compare = equals; + } else { + compare = function(a,b) { return a === b; }; + } parentSet = parentGet.assign || function() { // reset the change, or we will throw this exception on every $digest lastValue = isolateScope[scopeName] = parentGet(scope); @@ -1433,10 +1438,9 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { lastValue = isolateScope[scopeName] = parentGet(scope); isolateScope.$watch(function parentValueWatch() { var parentValue = parentGet(scope); - - if (parentValue !== isolateScope[scopeName]) { + if (!compare(parentValue, isolateScope[scopeName])) { // we are out of sync and need to copy - if (parentValue !== lastValue) { + if (!compare(parentValue, lastValue)) { // parent changed and it has precedence isolateScope[scopeName] = parentValue; } else { @@ -1445,7 +1449,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { } } return lastValue = parentValue; - }); + }, null, parentGet.literal); break; case '&': diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index f2fa4ef66863..853290a637ba 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -2492,6 +2492,62 @@ describe('$compile', function() { expect(lastRefValueInParent).toBe('new'); })); + + describe('literal objects', function() { + it('should copy parent changes', inject(function() { + compile('
'); + + $rootScope.name = 'a'; + $rootScope.$apply(); + expect(componentScope.reference).toEqual({name: 'a'}); + + $rootScope.name = 'b'; + $rootScope.$apply(); + expect(componentScope.reference).toEqual({name: 'b'}); + })); + + it('should not change the component when parent does not change', inject(function() { + compile('
'); + + $rootScope.name = 'a'; + $rootScope.$apply(); + var lastComponentValue = componentScope.reference; + $rootScope.$apply(); + expect(componentScope.reference).toBe(lastComponentValue); + })); + + it('should complain when the component changes', inject(function() { + compile('
'); + + $rootScope.name = 'a'; + $rootScope.$apply(); + componentScope.reference = {name: 'b'}; + expect(function() { + $rootScope.$apply(); + }).toThrowMinErr("$compile", "nonassign", "Expression '{name: name}' used with directive 'myComponent' is non-assignable!"); + + })); + + it('should work for primitive literals', inject(function() { + test('1', 1); + test('null', null); + test('undefined', undefined); + test("'someString'", 'someString'); + + + function test(literalString, literalValue) { + compile('
'); + + $rootScope.$apply(); + expect(componentScope.reference).toBe(literalValue); + dealoc(element); + + } + + })); + + }); + });