Angular2 and SVG
Posted on 2017-02-21 in Blog Last modified on: 2017-03-06
Note
I use the Aurelia framework, a competitor of Angular2. I wrote several articles about Aurelia. I am not an expert with Angular2. If you spot a mistake, please leave a comment.
In an interview about a year old, Rob Eisenberg, the creator of the Aurelia framework, said:
Over a year ago the Angular 2 team introduced their symbolic binding syntax. While that was technically standards compliant HTML, it was pointed out by the community that it was not compliant SVG (I have not confirmed that myself). Although members of the community pointed this out, the Angular 2 team made no changes to their design.
Since I recently completed a book about Angular2 and that I heavily use SVG in my applications, I wanted to test it myself. So I created an app with the cli (you can install it with npm install -g angular-cli@1.0.0-beta.22-1). Nothing fancy, just ng new test-svg. I then created a small SVG with Inkscape (it just contains a rectangle) and copied it into the template of the application.
I ran ng serve to build the application, opened it in a browser and BOOM, first error:
zone.js:388Unhandled Promise rejection: Template parse errors: ':sodipodi:namedview' is not a known element:
From what I know, Angular2 has its own HTML parser (mostly to parse camel cased syntax like ngIf which is not HTML compliant) and it fails if it encounters an HTML tag or attribute it doesn't know. While it's nice to help you spot typos in the name of your component, if a piece of HTML or SVG has a non standard tag or attribute, it will crash. Here it is not a big deal, my SVG is small, I can get rid of <sodipodi:namedview /> and <metadata />. But in one of my application, I need to display user uploaded SVGs. Once uploaded, I add AngularJS tags so I can display only certain elements of it depending on actions of the user. These SVGs will contain non standard tags like the Inkscape ones. How can I get rid of all of them so that Angular2 is happy? Whitelist? But what should I put in it? Blacklist? Same question. There are many SVG software out there, each may have its quirks.
This made me wonder: what if I use a brand new HTML 5.1 tag? Will it crash? I tried the code below, and it worked. I guess the parser is already 5.1 compliant. But to support 5.2 you will probably need to update Angular.
<details> // Start Of A New Accordion Element <summary> TITLE OF ACCORDION ELEMENT </summary> // Anything Here Will Be Displayed After Accordion Is Toggled To Open <p> Cras dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean lacinia mauris vel est. </p> </details>
Now that we have a working SVG, it is time to add Angular2 markup. Let's start simple and add an ngIf attribute to show/hide the SVG based on the value of a variable:
<svg *ngIf="showSvg"
Let's add a button to toggle the value of the SVG:
<button (click)="toggleSvg()">Toggle SVG</button>
And the toggleSvg function:
toggleSvg() { this.showSvg = !this.showSvg; }
And this works. The SVG is correctly displayed/hidden when I press the button.
Next step: hide the SVG when I click on the rectangle. Let's add the proper attribute:
<rect (click)="toggleSvg()" … />
If I click on the rectangle, the SVG is hidden as expected.
Next step: display multiple rectangles. Let's add a rectTops property to the component. It contains the y value for SVG rectangles. We will loop over it to display a rectangle for each value (and in a moment, we will try to bind the value to the y attribute of our rectangles).
rectTops = [30, 50, 70, 90];
<rect *ngFor="let top of rectTops" (click)="hideSvg()" … />
If we inspect the DOM, we see all the rectangles in it. Now, let's use the value:
<rect *ngFor="let top of rectTops" (click)="hideSvg()" [y]="top" … />
And it fails:
Unhandled Promise rejection: Template parse errors: Can't bind to 'y' since it isn't a known property of ':svg:rect'. ("
You may say, no problem, let's use the mustache notation instead:
<rect *ngFor="let top of rectTops" (click)="hideSvg()" y="{{ top }}" … />
No luck, it crashes too with the same error. And bind-y="top"? No luck either, still the same error.
It appears that the correct way to do this is:
<rect *ngFor="let top of rectTops" (click)="hideSvg()" [attr.y]="top" … />
Thanks NexusVI.
History
- 2017-03-06: Give the solution of how to use binding with SVG attributes. Thanks NexusVI.