From cf17c6af475eace31cf52944afd8e10d3afcf6c0 Mon Sep 17 00:00:00 2001
From: Luis Ramón López
Date: Tue, 26 Feb 2013 00:00:47 +0100
Subject: 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:
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:
Closes #1050
Closes #1925
---
docs/content/guide/directive.ngdoc | 27 ++++++++++++++++++++++++++-
src/ng/compile.js | 10 ++++++++--
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.
-# 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:
Hello {{username}}!
+
+# 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:
+
+
+
+and model cx set to 5, will result in rendering this dom:
+
+
+
+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('')($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('')($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('')($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');
+ }));
+ });
});
--
cgit v1.2.3