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

Commit cf17c6a

Browse files
lrlopezIgorMinar
authored andcommitted
feat($compile): add attribute binding support via ngAttr*
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
1 parent 86d191e commit cf17c6a

File tree

3 files changed

+71
-3
lines changed

3 files changed

+71
-3
lines changed

docs/content/guide/directive.ngdoc

+26-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ the following example.
5353
</doc:scenario>
5454
</doc:example>
5555

56-
# String interpolation
56+
# Text and attribute bindings
5757

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

69+
70+
# ngAttr attribute bindings
71+
72+
If an attribute with a binding is prefixed with `ngAttr` prefix (denormalized prefix: 'ng-attr-',
73+
'ng:attr-') then during the compilation the prefix will be removed and the binding will be applied
74+
to an unprefixed attribute. This allows binding to attributes that would otherwise be eagerly
75+
processed by browsers in their uncompilled form (e.g. `img[src]` or svg's `circle[cx]` attributes).
76+
77+
For example, considering template:
78+
79+
<svg>
80+
<circle ng-attr-cx="{{cx}}"></circle>
81+
</svg>
82+
83+
and model cx set to 5, will result in rendering this dom:
84+
85+
<svg>
86+
<circle cx="5"></circle>
87+
</svg>
88+
89+
If you were to bind `{{cx}}` directly to the `cx` attribute, you'd get the following error:
90+
`Error: Invalid value for attribute cx="{{cx}}"`. With `ng-attr-cx` you can work around this
91+
problem.
92+
93+
6994
# Compilation process, and directive matching
7095

7196
Compilation of HTML happens in three phases:

src/ng/compile.js

+8-2
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,8 @@ function $CompileProvider($provide) {
349349
? identity
350350
: function denormalizeTemplate(template) {
351351
return template.replace(/\{\{/g, startSymbol).replace(/}}/g, endSymbol);
352-
};
352+
},
353+
NG_ATTR_BINDING = /^ngAttr[A-Z]/;
353354

354355

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

516517
// iterate over the attributes
517-
for (var attr, name, nName, value, nAttrs = node.attributes,
518+
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
518519
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
519520
attr = nAttrs[j];
520521
if (attr.specified) {
521522
name = attr.name;
523+
// support ngAttr attribute binding
524+
ngAttrName = directiveNormalize(name);
525+
if (NG_ATTR_BINDING.test(ngAttrName)) {
526+
name = ngAttrName.substr(6).toLowerCase();
527+
}
522528
nName = directiveNormalize(name.toLowerCase());
523529
attrsMap[nName] = name;
524530
attrs[nName] = value = trim((msie && name == 'href')

test/ng/compileSpec.js

+37
Original file line numberDiff line numberDiff line change
@@ -2591,4 +2591,41 @@ describe('$compile', function() {
25912591
});
25922592
});
25932593
});
2594+
2595+
describe('ngAttr* attribute binding', function() {
2596+
2597+
it('should bind after digest but not before', inject(function($compile, $rootScope) {
2598+
$rootScope.name = "Misko";
2599+
element = $compile('<span ng-attr-test="{{name}}"></span>')($rootScope);
2600+
expect(element.attr('test')).toBeUndefined();
2601+
$rootScope.$digest();
2602+
expect(element.attr('test')).toBe('Misko');
2603+
}));
2604+
2605+
2606+
it('should work with different prefixes', inject(function($compile, $rootScope) {
2607+
$rootScope.name = "Misko";
2608+
element = $compile('<span ng:attr:test="{{name}}" ng-Attr-test2="{{name}}" ng_Attr_test3="{{name}}"></span>')($rootScope);
2609+
expect(element.attr('test')).toBeUndefined();
2610+
expect(element.attr('test2')).toBeUndefined();
2611+
expect(element.attr('test2')).toBeUndefined();
2612+
$rootScope.$digest();
2613+
expect(element.attr('test')).toBe('Misko');
2614+
expect(element.attr('test2')).toBe('Misko');
2615+
expect(element.attr('test3')).toBe('Misko');
2616+
}));
2617+
2618+
2619+
it('should work if they are prefixed with x- or data-', inject(function($compile, $rootScope) {
2620+
$rootScope.name = "Misko";
2621+
element = $compile('<span data-ng-attr-test2="{{name}}" x-ng-attr-test3="{{name}}" data-ng:attr-test4="{{name}}"></span>')($rootScope);
2622+
expect(element.attr('test2')).toBeUndefined();
2623+
expect(element.attr('test3')).toBeUndefined();
2624+
expect(element.attr('test4')).toBeUndefined();
2625+
$rootScope.$digest();
2626+
expect(element.attr('test2')).toBe('Misko');
2627+
expect(element.attr('test3')).toBe('Misko');
2628+
expect(element.attr('test4')).toBe('Misko');
2629+
}));
2630+
});
25942631
});

0 commit comments

Comments
 (0)