Thursday, September 18, 2014

Working with SVG in Angular

I don't have a good picture, so here's Igor with an SVG mustache.

Recently I posted about how to handle SVG in Ember. Angular has some issues itself with SVG, and it's worth discussing here, partially because I recently contributed a lot to the effort to get SVG working in Angular.

For the most part, Angular won't have too many problems with SVG, as long as you're using attribute-style directives, and you're not creating new directives. The issues Angular has with SVG will become more apparent when you're dealing with custom directives, particularly those using custom-elements, transclusion or template strings.

Angular 1.3.0-beta.19 has first class SVG support

The short story is, if you want to use SVG with Angular, it's a good idea to use 1.3.0 beta 19. I submitted and issue, then did a late-night hack-session with Igor Minar get SVG working in Angular. The approach was basically to track namespace while Angular was traversing the DOM looking for directives during $compile. The code in that area is a little tangled because of how it uses closures to pass around transclusions, found directives, and other important pieces of information. After we had it mostly working, Tobias Bosch and Igor (and probably others on the team) went back through the code and cleaned it up, fixing a few issues we didn't find the first time.

So what about 1.2?

The current stable version is 1.2. It is my personal opinion that if you want SVG support, at this point you're better off upgrading to 1.3.0-beta.19. This is because the hacks you'd have to perform to get SVG 100% working with Angular 1.2 would be much more than the work it would take to upgrade your app, and any subsequent beta-related code breakage.

Angular's issues with SVG

The problems with SVG in Angular are the same problems found in almost any framework, with one interesting exception, cloning custom elements.

1. Parsing HTML

This happened in cases where template strings were being converted into DOM elements so they could be compiled.  Parsing HTML strings was done with a wrap map that didn't support SVG elements. I went through what a wrap map is in my previous post about Ember and SVG, but as a refresher: Basically, you create a div object, then you set it's innerHTML to your HTML string, which is sandwiched between the proper tags. So if you had a string like '<td>whatever</td>', you'd create an HTMLDivElement with document.createElement, then you'd concat '<table><tbody><tr>', your string and the closing tags, then assign it to div.innerHTML. After than you'd grab the nodes from the appropriate place in the DOM tree of div.

2. Cloning custom elements

The reason this one is interesting is because Angular leverages the existing DOM as it's view, where other frameworks process templates and output DOM. This means that Angular can do efficient things like clone existing elements to create new elements. The also means that Angular is going to try to clone everything, even custom tags, exactly as they exist in the DOM. SVG 1 doesn't know what to do with custom tags. SVG 2 will treat them like <g> tags, but no browser supports SVG 2 yet, AFAIK.  So what happens when you clone <my-tag><circle /></my-tag>? It clones successfully, but <my-tag> has no layout, so everything underneath it will not render. This means that all directives that are custom elements for SVG need to have `replace:true`. This is something that is true even in Angular 1.3.0 beta 19 and higher.

3. SVG validation error messages

Since Angular is using real DOM to set up it's views, that means you're liable to run into situations where you're trying to bind to an attribute in svg like `<rect x="{{x}}"/>` The problem here is you'll get error messages all over your console saying that {{x}} is an invalid value for rect (or something like that). Fortunately, there is already a fix in angular for this in the form of the `ng-attr-` binding style. That means you can do `<rect ng-attr-x="{{x}}"/>` and everything will will be fine.

Custom SVG elements in Angular 1.3.0-beta.19 and higher

There is a new `templateNamespace` property in the directive configuration object that you'll need to set, and you'll want to set `replace` to true. But other than that, it's now pretty straight forward.

Here's an example:


  1. Did you try to do AngularJS in an SVG file instead of HTML? Forms might be a bit complicated in SVG though...

    1. You'd still run into problems with cloning custom elements that exist within the SVG. for example. SVG 1.1 doesn't know how to render custom elements, so it just doesn't render them at all.

  2. I cannot even get a native SVG file to render without console error messages with angular simply loaded as a resource via a script tag. I assume all of the above made it into the official 1.3.0 release which is what I'm using here:

    <?xml version="1.0" encoding="utf-8"?>

    <svg width="640" height="360" xmlns="" xmlns:xlink="">
    <script type="text/ecmascript" xlink:href="lib/angular/angular.min.js"></script>
    <!-- ... -->

  3. Hope, this js fragment shows up properly, but without going through the trouble of building angular from source, I added this bit to line 16058 of the un-minified 'angular.js':

    /* REV EDIT:
    * Fix pathname parsing...
    if( href.match(/^https?:\/\/(?:[^:@\/]+(?::[^@\/]+)?@)?([\w|\-|\.]+)(?::\d+)?(?:\/.*)?$/) !== null ) {
    var parts = href.match(/^https?:\/\/(?:[^:@\/]+(?::[^@\/]+)?@)?([\w|\-|\.]+)(?::\d+)?(\/.*)?$/); = parts[1];
    urlParsingNode.pathname = parts[2];
    /* END EDIT */

  4. Thanks for this post Ben. This is helpful one.

    "There is a high demand for data visualization integration with single page applications, to be sure." - You have made really very correct statement over this issue discussion (github #8494).

  5. templateNamespace: 'svg' was causing an error:

    Error: [NG-Modular Error] [$compile:tplrt] Template for directive 'customsvg' must have exactly one root element.

    Ended up removing templateNamespace and it worked fine.


This form allows some basic HTML. It will only create links if you wrap the URL in an anchor tag (Sorry, it's the Blogger default)