diff options
| author | Luis Ramón López | 2013-02-26 00:00:47 +0100 |
|---|---|---|
| committer | Igor Minar | 2013-02-27 00:55:40 -0800 |
| commit | cf17c6af475eace31cf52944afd8e10d3afcf6c0 (patch) | |
| tree | aa552c9f6b8af0ff0ed208ec6c3a5574432a4109 | |
| parent | 86d191ed4aea9015adc71b852223475c5c762c34 (diff) | |
| download | angular.js-cf17c6af475eace31cf52944afd8e10d3afcf6c0.tar.bz2 | |
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
| -rw-r--r-- | docs/content/guide/directive.ngdoc | 27 | ||||
| -rw-r--r-- | src/ng/compile.js | 10 | ||||
| -rw-r--r-- | test/ng/compileSpec.js | 37 |
3 files changed, 71 insertions, 3 deletions
diff --git a/docs/content/guide/directive.ngdoc b/docs/content/guide/directive.ngdoc index da1dc6df..59b89b40 100644 --- a/docs/content/guide/directive.ngdoc +++ b/docs/content/guide/directive.ngdoc @@ -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 @@ -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: diff --git a/src/ng/compile.js b/src/ng/compile.js index 48beb61e..3746bb66 100644 --- a/src/ng/compile.js +++ b/src/ng/compile.js @@ -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; @@ -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') diff --git a/test/ng/compileSpec.js b/test/ng/compileSpec.js index b9ed3ff9..6f56c6e6 100644 --- a/test/ng/compileSpec.js +++ b/test/ng/compileSpec.js @@ -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'); + })); + }); }); |
