diff --git a/docs/content/guide/directive.ngdoc b/docs/content/guide/directive.ngdoc
index 2f93ef33d12c..fb8a63780845 100644
--- a/docs/content/guide/directive.ngdoc
+++ b/docs/content/guide/directive.ngdoc
@@ -486,6 +486,9 @@ link() or compile() functions - is a way of accessing:
such as 'ng:bind', or 'x-ng-bind', the attributes object allows for normalized accessed to
the attributes.
+ * *late binding attributes: If the attribute is prefixed with 'ng-attr-', 'ng:attr:' or 'ng_attr_'
+ the prefix will be removed once the attribute is evaluated, allowing late binding.
+
* *directive inter-communication:* All directives share the same instance of the attributes
object which allows the directives to use the attributes object as inter directive
communication.
diff --git a/src/ng/compile.js b/src/ng/compile.js
index 11a86f3fd558..e259f1c70a1c 100644
--- a/src/ng/compile.js
+++ b/src/ng/compile.js
@@ -514,11 +514,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, lName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
attr = nAttrs[j];
if (attr.specified) {
name = attr.name;
+ // support late binding attributes
+ lName = directiveNormalize(name);
+ if (/ngAttr[A-Z]/.test(lName)) {
+ name = lName.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 d2eddafcba78..c7c000a8b3be 100644
--- a/test/ng/compileSpec.js
+++ b/test/ng/compileSpec.js
@@ -2498,4 +2498,40 @@ describe('$compile', function() {
});
});
});
+
+ describe('late binding attributes', 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');
+ }));
+ });
+
});