Skip to content

Commit

Permalink
feat($compile): Allow late binding attributes
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.

I.e:
<svg>
  <circle ng_attr_cx="{{cx}}" ng-attr-cy="{{cy}}" ng:Attr:r="{{r}}"></circle>
</svg>

Closes angular#1050
Closes angular#1925
  • Loading branch information
lrlopez committed Feb 25, 2013
1 parent 2508b47 commit 638e6fd
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 1 deletion.
3 changes: 3 additions & 0 deletions docs/content/guide/directive.ngdoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
7 changes: 6 additions & 1 deletion src/ng/compile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand Down
36 changes: 36 additions & 0 deletions test/ng/compileSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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('<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');
}));
});

});

4 comments on commit 638e6fd

@JDvorak
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not play well with data- prefix.

@lrlopez
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

John, can you show me a non-working example? A Plunker link would be nice...

@shmert
Copy link

@shmert shmert commented on 638e6fd Aug 27, 2014

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You cannot set namespaced attributes this way, e.g.

<svg
        width="483"
        height="208"
        xmlns:xlink="http://www.w3.org/1999/xlink"
        xmlns="http://www.w3.org/2000/svg">
    <image ng:attr:xlink:href="{{'https://angularjs.org/img/AngularJS-large.png' | unsafe}}" ng:attr:x="{{50}}" y="50" width="383" height="108"/>
</svg>

The x attribute is set correctly.

However, the xlink:href is broken. This example will set an attribute of xlink-href on the image tag, instead of xlink:href. Maybe this is unavoidable, but it would be handy if late binding supported this.

@lrlopez
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Angular normalization won't allow you setting namespaced attributes. Please, have a look into angular#1925 where some alternatives are shown. The post at the bottom has a gist that implements a new directive which could be useful to you.

Please sign in to comment.