title: Rendering SVG templates in AngularJS directives
body: <p>I'm working on a large project with SVG and angular.js, and need solid support for svg directive templates. Unfortunately when angular renders the templates, it creates DOM nodes, not SVG nodes. My current work around is to manage creating and deleting the nodes myself using jquery.svg, but its coming to it limits. Example: <a href="http://plnkr.co/edit/Xk8wM3?p=preview" rel="nofollow">http://plnkr.co/edit/Xk8wM3?p=preview</a></p>
  
  <p>I would like to have the directives <code>element</code> be the actual svg element, not some faux DOM element that doesn't really do anything. This will let me use ng-repeat and angular filters effectively.</p>
  
  <p>Here is the plunkr that needs fixing: <a href="http://plnkr.co/edit/BPvGjf?p=preview" rel="nofollow">http://plnkr.co/edit/BPvGjf?p=preview</a></p>
  
  <p><strong>html</strong></p>
  
  <pre><code>&lt;svg&gt;
    &lt;!--woot this one works--&gt;
    &lt;shape d="M0,0L250,0L250,250L0,250z" fill="green"&gt;&lt;/shape&gt;
  
    &lt;!--nesting directives doesn't work--&gt;
    &lt;group&gt;
      &lt;shape d="M0,0L150,0L150,150L0,150z" fill="red"&gt;&lt;/shape&gt;
      &lt;shape d="M0,0L100,0L100,100L0,100z" fill="orange"&gt;&lt;/shape&gt;
    &lt;/group&gt;
  
    &lt;!--ng repeat doesn't work--&gt;
    &lt;shape d="{{square.path}}" fill="{{square.color}}" ng-repeat="square in squares | orderBy:'order'"&gt;&lt;/shape&gt;
  &lt;/svg&gt;
  </code></pre>
  
  <p><strong>javascript</strong></p>
  
  <pre><code>var app = angular.module('plunker', []);
  
  app.controller('MainCtrl', function($scope) {
    $scope.squares = [{
      path: "M0,0L50,0L50,50L0,50z",
      color: 'blue',
      order: 2
    }, {
      path: "M0,0L75,0L75,75L0,75z",
      color: 'purple',
      order: 1
    }];
  });
  
  app.directive('group', function($compile) {
    return {
      restrict: 'E',
      transclude: true,
      compile: function(tElement, tAttr, transclude) {
        var path = makeNode('g', tElement, tAttr);
  
        tElement.append(path.cloneNode(true));
  
        return function(scope, element) {
          transclude(scope, function(clone, innerScope) {
            element.append($compile(clone)(innerScope));
          })
        }
      }
    }
  });
  
  app.directive('shape', function() {
    return {
      restrict: 'E',
      compile: function(tElement, tAttr) {
        var path = makeNode('path', tElement, tAttr);
  
        tElement.replaceWith(path.cloneNode(true));
  
        return function(scope, element) {
  
        }
      }
    }
  })
  
  /* Create a shape node with the given settings. */
  function makeNode(name, element, settings) {
    var svg = $(element).parents('svg')[0];
    var parent = $(element).parent()[0];
    var factory = $('#factory')[0] || $('body').append('&lt;svg id="factory"&gt;&lt;/svg&gt;').find('#factory')[0];
    var ns = 'http://www.w3.org/2000/svg';
  
    // I've tried using the factory here to create the node then clone
    // it into the new svg, but since angular nodes are created child-first, it didn't work
  
    var node = parent.ownerDocument.createElementNS(ns, name);
    for (var attribute in settings) {
      var value = settings[attribute];
      if (value !== null &amp;&amp; value !== null &amp;&amp; !attribute.match(/\$/) &amp;&amp;
        (typeof value !== 'string' || value !== '')) {
        node.setAttribute(attribute, value);
      }
    }
    parent.appendChild(node);
    return node;
  }
  </code></pre>