From d7d30a3c60a3983240697939a6449ca6a6286fd4 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Sat, 7 Jun 2014 00:44:46 -0700 Subject: [PATCH] fix($compile): bind ng-attr-* even if unbound attribute follows ng-attr-* Previously, 's "foo" attribute would always equal "bar", because the bound version was overwritten. This CL corrects this behaviour and ensures that the ordering of attributes does not have an effect on whether or not ng-attr-bound attributes do their work. Closes #7739 --- src/ng/compile.js | 14 +++++--- test/ng/compileSpec.js | 77 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) diff --git a/src/ng/compile.js b/src/ng/compile.js index 56d36003f70b..aaef78ea1ca9 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -1010,7 +1010,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority, ignoreDirective); // iterate over the attributes - for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes, + for (var attr, name, nName, ngAttrName, value, isNgAttr, nAttrs = node.attributes, j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) { var attrStartName = false; var attrEndName = false; @@ -1018,9 +1018,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { attr = nAttrs[j]; if (!msie || msie >= 8 || attr.specified) { name = attr.name; + value = trim(attr.value); + // support ngAttr attribute binding ngAttrName = directiveNormalize(name); - if (NG_ATTR_BINDING.test(ngAttrName)) { + if (isNgAttr = NG_ATTR_BINDING.test(ngAttrName)) { name = snake_case(ngAttrName.substr(6), '-'); } @@ -1033,9 +1035,11 @@ function $CompileProvider($provide, $$sanitizeUriProvider) { nName = directiveNormalize(name.toLowerCase()); attrsMap[nName] = name; - attrs[nName] = value = trim(attr.value); - if (getBooleanAttrName(node, nName)) { - attrs[nName] = true; // presence means true + if (isNgAttr || !attrs.hasOwnProperty(nName)) { + attrs[nName] = value; + if (getBooleanAttrName(node, nName)) { + attrs[nName] = true; // presence means true + } } addAttrInterpolateDirective(node, directives, value, nName); addDirective(directives, nName, 'A', maxPriority, ignoreDirective, attrStartName, diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index 480bde531a1d..6a044d1a953c 100755 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -4901,6 +4901,83 @@ describe('$compile', function() { expect(element.attr('test')).toBe('Misko'); })); + it('should bind after digest but not before when after overridden attribute', inject(function($compile, $rootScope) { + $rootScope.name = "Misko"; + element = $compile('')($rootScope); + expect(element.attr('test')).toBe('123'); + $rootScope.$digest(); + expect(element.attr('test')).toBe('Misko'); + })); + + it('should bind after digest but not before when before overridden attribute', inject(function($compile, $rootScope) { + $rootScope.name = "Misko"; + element = $compile('')($rootScope); + expect(element.attr('test')).toBe('123'); + $rootScope.$digest(); + expect(element.attr('test')).toBe('Misko'); + })); + + + describe('in directive', function() { + beforeEach(module(function() { + directive('syncTest', function(log) { + return { + link: { + pre: function(s, e, attr) { log(attr.test); }, + post: function(s, e, attr) { log(attr.test); } + } + }; + }); + directive('asyncTest', function(log) { + return { + templateUrl: 'async.html', + link: { + pre: function(s, e, attr) { log(attr.test); }, + post: function(s, e, attr) { log(attr.test); } + } + }; + }); + })); + + beforeEach(inject(function($templateCache) { + $templateCache.put('async.html', '

Test

'); + })); + + it('should provide post-digest value in synchronous directive link functions when after overridden attribute', + inject(function(log, $rootScope, $compile) { + $rootScope.test = "TEST"; + element = $compile('
')($rootScope); + expect(element.attr('test')).toBe('123'); + expect(log.toArray()).toEqual(['TEST', 'TEST']); + })); + + it('should provide post-digest value in synchronous directive link functions when before overridden attribute', + inject(function(log, $rootScope, $compile) { + $rootScope.test = "TEST"; + element = $compile('
')($rootScope); + expect(element.attr('test')).toBe('123'); + expect(log.toArray()).toEqual(['TEST', 'TEST']); + })); + + + it('should provide post-digest value in asynchronous directive link functions when after overridden attribute', + inject(function(log, $rootScope, $compile) { + $rootScope.test = "TEST"; + element = $compile('
')($rootScope); + expect(element.attr('test')).toBe('123'); + $rootScope.$digest(); + expect(log.toArray()).toEqual(['TEST', 'TEST']); + })); + + it('should provide post-digest value in asynchronous directive link functions when before overridden attribute', + inject(function(log, $rootScope, $compile) { + $rootScope.test = "TEST"; + element = $compile('
')($rootScope); + expect(element.attr('test')).toBe('123'); + $rootScope.$digest(); + expect(log.toArray()).toEqual(['TEST', 'TEST']); + })); + }); it('should work with different prefixes', inject(function($compile, $rootScope) { $rootScope.name = "Misko";