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

Commit

Permalink
feat($compile): add attribute binding support via ngAttr*
Browse files Browse the repository at this point in the history
Sometimes is not desirable to use interpolation on attributes because
the user agent parses them before the interpolation takes place. I.e:

<svg>
  <circle cx="{{cx}}" cy="{{cy}}" r="{{r}}"></circle>
</svg>

The snippet throws three browser errors, one for each attribute.

For some attributes, AngularJS fixes that behaviour introducing special
directives like ng-href or ng-src.

This commit is a more general solution that allows prefixing any
attribute with "ng-attr-", "ng:attr:" or "ng_attr_"  so it will
be set only when the binding is done. The prefix is then removed.

Example usage:

<svg>
  <circle ng-attr-cx="{{cx}}" ng-attr-cy="{{cy}}" ng:attr-r="{{r}}"></circle>
</svg>

Closes #1050
Closes #1925
  • Loading branch information
lrlopez authored and IgorMinar committed Feb 27, 2013
1 parent 86d191e commit cf17c6a
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 3 deletions.
27 changes: 26 additions & 1 deletion docs/content/guide/directive.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ the following example.
</doc:scenario>
</doc:example>

# String interpolation
# Text and attribute bindings

During the compilation process the {@link api/ng.$compile compiler} matches text and
attributes using the {@link api/ng.$interpolate $interpolate} service to see if they
Expand All @@ -66,6 +66,31 @@ here:
<a href="img/{{username}}.jpg">Hello {{username}}!</a>
</pre>


# ngAttr attribute bindings

If an attribute with a binding is prefixed with `ngAttr` prefix (denormalized prefix: 'ng-attr-',
'ng:attr-') then during the compilation the prefix will be removed and the binding will be applied
to an unprefixed attribute. This allows binding to attributes that would otherwise be eagerly
processed by browsers in their uncompilled form (e.g. `img[src]` or svg's `circle[cx]` attributes).

For example, considering template:

<svg>
<circle ng-attr-cx="{{cx}}"></circle>
</svg>

and model cx set to 5, will result in rendering this dom:

<svg>
<circle cx="5"></circle>
</svg>

If you were to bind `{{cx}}` directly to the `cx` attribute, you'd get the following error:
`Error: Invalid value for attribute cx="{{cx}}"`. With `ng-attr-cx` you can work around this
problem.


# Compilation process, and directive matching

Compilation of HTML happens in three phases:
Expand Down
10 changes: 8 additions & 2 deletions src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ function $CompileProvider($provide) {
? identity
: function denormalizeTemplate(template) {
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
};
},
NG_ATTR_BINDING = /^ngAttr[A-Z]/;


return compile;
Expand Down Expand Up @@ -514,11 +515,16 @@ function $CompileProvider($provide) {
directiveNormalize(nodeName_(node).toLowerCase()), 'E', maxPriority);

// iterate over the attributes
for (var attr, name, nName, value, nAttrs = node.attributes,
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
attr = nAttrs[j];
if (attr.specified) {
name = attr.name;
// support ngAttr attribute binding
ngAttrName = directiveNormalize(name);
if (NG_ATTR_BINDING.test(ngAttrName)) {
name = ngAttrName.substr(6).toLowerCase();
}
nName = directiveNormalize(name.toLowerCase());
attrsMap[nName] = name;
attrs[nName] = value = trim((msie && name == 'href')
Expand Down
37 changes: 37 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2591,4 +2591,41 @@ describe('$compile', function() {
});
});
});

describe('ngAttr* attribute binding', function() {

it('should bind after digest but not before', inject(function($compile, $rootScope) {
$rootScope.name = "Misko";
element = $compile('<span ng-attr-test="{{name}}"></span>')($rootScope);
expect(element.attr('test')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('test')).toBe('Misko');
}));


it('should work with different prefixes', inject(function($compile, $rootScope) {
$rootScope.name = "Misko";
element = $compile('<span ng:attr:test="{{name}}" ng-Attr-test2="{{name}}" ng_Attr_test3="{{name}}"></span>')($rootScope);
expect(element.attr('test')).toBeUndefined();
expect(element.attr('test2')).toBeUndefined();
expect(element.attr('test2')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('test')).toBe('Misko');
expect(element.attr('test2')).toBe('Misko');
expect(element.attr('test3')).toBe('Misko');
}));


it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
$rootScope.name = "Misko";
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}"></span>')($rootScope);
expect(element.attr('test2')).toBeUndefined();
expect(element.attr('test3')).toBeUndefined();
expect(element.attr('test4')).toBeUndefined();
$rootScope.$digest();
expect(element.attr('test2')).toBe('Misko');
expect(element.attr('test3')).toBe('Misko');
expect(element.attr('test4')).toBe('Misko');
}));
});
});

0 comments on commit cf17c6a

Please sign in to comment.