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: http://plnkr.co/edit/Xk8wM3?p=preview
I would like to have the directives element
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.
Here is the plunkr that needs fixing: http://plnkr.co/edit/BPvGjf?p=preview
html
<svg>
<!--woot this one works-->
<shape d="M0,0L250,0L250,250L0,250z" fill="green"></shape>
<!--nesting directives doesn't work-->
<group>
<shape d="M0,0L150,0L150,150L0,150z" fill="red"></shape>
<shape d="M0,0L100,0L100,100L0,100z" fill="orange"></shape>
</group>
<!--ng repeat doesn't work-->
<shape d="{{square.path}}" fill="{{square.color}}" ng-repeat="square in squares | orderBy:'order'"></shape>
</svg>
javascript
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('<svg id="factory"></svg>').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 && value !== null && !attribute.match(/\$/) &&
(typeof value !== 'string' || value !== '')) {
node.setAttribute(attribute, value);
}
}
parent.appendChild(node);
return node;
}
I have forked and updated your plunker to make it work here. Your function 'makeNode' was throwing error sometimes when the node in question was detached from document by angular for processing. Using a timeout of 0 ms does the trick of delaying the execution just enough to make the parent node available. Also, I am using the linking function to do all manipulation because the compile function executes only once per ng-repeat but we need it repeated number of times. You can read more about the difference between compile and linking functions under 'The difference between Compile and Link' section on Angular Directive page.
Here is the relevant code:
/* Create a shape node with the given settings. */
function makeNode(name, element, settings) {
var ns = 'http://www.w3.org/2000/svg';
var node = document.createElementNS(ns, name);
for (var attribute in settings) {
var value = settings[attribute];
if (value !== null && value !== null && !attribute.match(/\$/) &&
(typeof value !== 'string' || value !== '')) {
node.setAttribute(attribute, value);
}
}
return node;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With