From ed59370d805a88c9ac012a8e417faf2a9f902776 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. --- 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 56d36003f..aaef78ea1 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 480bde531..6a044d1a9 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";