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:
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:
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 = '';
// 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'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;
}
Share
Improve this question
edited Dec 15, 2014 at 18:58
isherwood
61.1k16 gold badges120 silver badges168 bronze badges
asked Oct 24, 2013 at 14:04
Jason MoreJason More
7,0736 gold badges44 silver badges53 bronze badges
1 Answer
Reset to default 16I 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;
}