Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix($compile): bind ng-attr-* even if unbound attribute follows ng-at…
Browse files Browse the repository at this point in the history
…tr-*

Previously, <element ng-attr-foo="{{binding}}" foo="bar"></element>'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.
  • Loading branch information
jbedard authored and caitp committed Jun 17, 2014
1 parent d8e5acf commit ed59370
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 5 deletions.
14 changes: 9 additions & 5 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -1010,17 +1010,19 @@ 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;

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), '-');
}

Expand All @@ -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,
Expand Down
77 changes: 77 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<span test="123" ng-attr-test="{{name}}"></span>')($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('<span ng-attr-test="{{name}}" test="123"></span>')($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', '<h1>Test</h1>');
}));

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('<div sync-test test="123" ng-attr-test="{{test}}"></div>')($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('<div sync-test ng-attr-test="{{test}}" test="123"></div>')($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('<div async-test test="123" ng-attr-test="{{test}}"></div>')($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('<div async-test ng-attr-test="{{test}}" test="123"></div>')($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";
Expand Down

0 comments on commit ed59370

Please sign in to comment.