7

I want to bind some data to non-custom html element attributes. However, the {{ }} in attributes is not extrapolated. I've looked at other related posts as "Angularjs templateUrl fails to bind attributes inside ng-repeat", which is a angular 1 solution for custom directives.

For example, I have:

size = 500;

I want the following SVG element to work properly:

<svg xmlns="http://www.w3.org/2000/svg/" width="{{size}}" height="{{size}}">
<rect width="{{size}}" height="{{size}}" fill="#DCB35C"/>
</svg>

How should I do this in Angular 2?

Community
  • 1
  • 1
lys1030
  • 283
  • 1
  • 5
  • 17

3 Answers3

12

Short answer

When there isn't an 1:1 mapping between an HTML attribute and a DOM property one must use the attribute binding syntax otherwise Angular 2 will report a "template parse error".

Examples:

  • [attr.my-custom-attribute]="myComponentValue"
  • [attr.colspan]="1 + 1"

In your case, an SVG element has the width and height DOM properties but they aren't what you expect. They're SVGAnimatedLength objects. Trying to set their value the old way won't do anything. That's why your template doesn't work as you expect and doesn't report an error. Switching to attribute binding syntax would fix this behaviour: [attr.width]="width" [attr.height]="height"

In depth explanation

There's a big conceptual difference between how attribute bindings work in Angular 1 and Angular 2.

In Angular 1, setting a custom attribute looks like this:

  • <div a-custom-attribute="I am a custom {{ 'attribute' }}">Text Content</div>
  • <div ng-attr-a-custom-attribute="I am a custom {{ 'attribute' }}">Text Content</div> - this syntax allows you to bind to attributes that would otherwise be eagerly processed by browsers (e.g. an SVG element's circle[cx] attributes, the src attribute of a IMG element, etc)

In Angular 2, there's a different story:

Angular 2 introduced, as they put it, a new mental model: instead of binding to HTML attributes it binds to DOM properties. Understanding the distinction between an HTML attribute and a DOM property is crucial to getting a grasp of how Angular 2 binding works.

Binding to a DOM property may looks like this:

  • <img [src]="heroImageUrl">
  • <img bind-src="heroImageUrl">
  • <img src="{{ heroImageUrl }}"> - this may look a bit confusing, especially if someone has an AngularJS 1 background, but Angular 2 translates these interpolations into the corresponding property bindings before rendering the view (source). It's important to have in mind that, as Mark pointed out in the comment section, after an interpolation has been evaluated, its result is then converted to a string (source). This implies that this syntax is limited to only assigning string values.

Note that if the name fails to match a DOM property, Angular 2 reports an "unknown native property" error:

// Template parse errors:
// Can't bind to 'colspan' since it isn't a known native property
<tr><td colspan="{{1 + 1}}">Three-Four</td></tr>

// Template parse errors:
// Can't bind to 'madeUpProperty' since it isn't a known native property
<div [madeUpProperty]="My custom {{ 'madeUpProperty' }}"></div>

This means that one must use the attribute binding syntax when there is no DOM property to bind to.

Finally, I believe that as a good rule of thumb one should always use the property binding's syntax (e.g. [src]="heroImageUrl") in favor of interpolation (e.g. src="{{heroImageUrl}}") whenever he wants to modify the element's DOM property since the latter is limited to only passing string values. Another reason is that if someone has an AngularJS 1 background, this should reduce the confusion between setting an attribute and a DOM property.

Community
  • 1
  • 1
Cosmin Ababei
  • 7,003
  • 2
  • 20
  • 34
  • 1
    There is a subtle difference between `x="{{template_expression}}"` and `[x]="template_expression"` -- they are not the same. With `{{}}` the result of the `template_expression` is converted to a string, then it is assigned to property `x`. With `[]`, there is no string conversion step. So I don't agree with the Angular docs that state "There is no technical reason to prefer one form to the other". If we want to assign something other than a string to property `x`, we can't use `{{}}`, we must use `[]`. – Mark Rajcok Mar 23 '16 at 20:06
  • @MarkRajcok Thanks for pointing that out. I've edited my answer. – Cosmin Ababei Mar 24 '16 at 09:20
4

You should bind width & height using attribute binding by having attr prefix before binding like [attr.*].

Markup

<svg xmlns="http://www.w3.org/2000/svg/" [attr.width]="size" [attr.height]="size">
  <rect [attr.width]="width" [attr.height]="height" fill="#DCB35C" />
</svg>

Class

import {Component} from 'angular2/core';
import {bootstrap} from 'angular2/platform/browser';

@Component({
  selector: 'demo-app',
  templateUrl: 'src/app.html',
  pipes: []
})
export class App {
  width: number = 100;
  height: number=100;
  size: number=100;
  constructor() { }
}

bootstrap(App);

Demo Plunkr


As requested appending value of size by having some constant string value, You just need to put that on attribute like [attr.width]="size + '5'"

<svg xmlns="http://www.w3.org/2000/svg/" [attr.width]="size" [attr.height]="size">
  <rect [attr.width]="width + '5'" [attr.height]="height + '5'" fill="#DCB35C" />
</svg>

Updated Plunkr

Pankaj Parkar
  • 134,766
  • 23
  • 234
  • 299
4
<svg xmlns="http://www.w3.org/2000/svg/" [attr.width.px]="size" [attr.height.px]="size">
  <rect [attr.width.px]="width" [attr.height.px]="height" fill="#DCB35C" />
</svg>
micronyks
  • 54,797
  • 15
  • 112
  • 146
  • `.px` is not really required..those are attribute which takes an value and then they internally applies that `width` & `height` over that `svg` & `rect` element. take a look at [this plunkr](http://plnkr.co/edit/mMtmDyH4yRYr3NcXZIzZ?p=preview) – Pankaj Parkar Mar 19 '16 at 08:35
  • I'm just notifying `px` aren't required there while drawing `svg` https://developer.mozilla.org/en-US/docs/Web/SVG/Element/rect – Pankaj Parkar Mar 19 '16 at 08:40
  • agreed. Please note it I haven't said it is required but cant be used that way also. – micronyks Mar 19 '16 at 08:43