Включение шаблона SVG в директиву Angularjs
<div ng-app="testrectApp">
<svg>
<rect height="10" width="10" style="fill: #00ff00" />
<testrect />
</svg>
</div>
И это моя директива
module.directive('testrect', function() {
return {
restrict: 'E',
template: '<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />',
replace: true
};
});
Но так выглядит элемент в браузере. Заполнение дважды не опечатка.
<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff;fill: #ff00ff"></rect>
Вот jsfiddle http://jsfiddle.net/RG2CF/
То, что я пытаюсь сделать, невозможно, или я делаю что-то не так?
Спасибо
РЕДАКТИРОВАТЬ: я должен добавить, что проблема, возможно, связана с тем фактом, что шаблон svg rect имеет пространство имен в xhtml вместо svg, но я не уверен, как заставить это или пространство имен вернуть его в svg, если это на самом деле решение.
5 ответов
E сть templateNamespace
свойство, которое вы можете установить svg
:
module.directive('testrect', function() {
return {
restrict: 'E',
templateNamespace: 'svg',
template: '<rect .../>',
replace: true
};
});
Вот ссылка на документацию.
Обновление Angular 1.3!
Мой оригинальный ответ ниже был в лучшем случае гигантским хаком. Что вы действительно должны сделать, это обновить до Angular 1.3 и установить templateNamespace:'svg'
собственность по вашей директиве. Посмотрите ниже на ответ @Josef Pfleger для примера: Включение шаблона SVG в директиву Angularjs
Старый ответ
На случай, если кто-нибудь столкнется с этим в будущем, я смог создать функцию компиляции, которая будет работать с шаблонами svg. Сейчас это немного уродливо, поэтому помощь в рефакторинге очень ценится.
живой пример: http://plnkr.co/edit/DN2M3M?p=preview
app.service('svgService', function(){
var xmlns = "http://www.w3.org/2000/svg";
var compileNode = function(angularElement){
var rawElement = angularElement[0];
//new lines have no localName
if(!rawElement.localName){
var text = document.createTextNode(rawElement.wholeText);
return angular.element(text);
}
var replacement = document.createElementNS(xmlns,rawElement.localName);
var children = angularElement.children();
angular.forEach(children, function (value) {
var newChildNode = compileNode(angular.element(value));
replacement.appendChild(newChildNode[0]);
});
if(rawElement.localName === 'text'){
replacement.textContent = rawElement.innerText;
}
var attributes = rawElement.attributes;
for (var i = 0; i < attributes.length ; i++) {
replacement.setAttribute(attributes[i].name, attributes[i].value);
}
angularElement.replaceWith(replacement);
return angular.element(replacement);
};
this.compile = function(elem, attrs, transclude) {
compileNode(elem);
return function postLink(scope, elem,attrs,controller){
// console.log('link called');
};
}
})
вариант использования:
app.directive('ngRectAndText', function(svgService) {
return {
restrict: 'E',
template:
'<g>'
+ '<rect x="140" y="150" width="25" height="25" fill="red"></rect>'
+ '<text x="140" y="150">Hi There</text>'
+ '</g>'
,
replace: true,
compile: svgService.compile
};
});
Использование прокладки innerHTML для SVG (также называемой "innerSVG") предоставляет еще один фрагмент этой головоломки в качестве альтернативы проницательному ответу Джейсона Мора. Я чувствую, что этот подход innerHTML/innerSVG более элегантен, потому что он не требует специальной функции компиляции, такой как ответ Джейсона, но у него есть по крайней мере один недостаток: он не работает, если в директиве задано "replace: true". (Подход innerSVG обсуждается здесь: есть ли какая-то замена innerHTML в SVG / XML?)
// Inspiration for this derived from a shim known as "innerSVG" which adds
// an "innerHTML" attribute to SVGElement:
Object.defineProperty(SVGElement.prototype, 'innerHTML', {
get: function() {
// TBD!
},
set: function(markup) {
// 1. Remove all children
while (this.firstChild) {
this.removeChild(this.firstChild);
}
// 2. Parse the SVG
var doc = new DOMParser().parseFromString(
'<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">'
+ markup
+ '</svg>',
'application/xml'
);
// 3. Import the parsed SVG and grab the childNodes
var contents = this.ownerDocument.importNode(doc.documentElement, true).childNodes;
// 4. Append the childNodes to this node
// Note: the following for loop won't work because as items are appended,
// they are removed from contents (you'd end up with only every other
// element getting appended and the other half remaining still in the
// original document tree):
//for (var i = 0; i < contents.length; i++) {
// this.appendChild(contents[i]);
//}
while (contents.length) {
this.appendChild(contents[0]);
}
},
enumerable: false,
configurable: true
});
Вот Plunker, чтобы попробовать: http://plnkr.co/edit/nEqfbbvpUCOVC1XbTKdQ?p=preview
Причина, по которой это работает: элементы SVG находятся в пространстве имен XML http://www.w3.org/2000/svg, и поэтому они должны быть проанализированы с помощью DOMParser() или созданы с помощью createElementNS() (как в ответе Джейсона), оба из которых могут установить пространство имен соответствующим образом. Было бы неплохо, если бы этот процесс использования SVG был таким же простым, как и HTML, но, к сожалению, их структуры DOM немного отличаются, что приводит к необходимости предоставления нашего собственного внутреннего HTMLHIM-кода. Теперь, если бы мы могли заставить "replace: true" работать с этим, это было бы что-то хорошее!
РЕДАКТИРОВАТЬ: Обновлен цикл appendChild(), чтобы обходное содержимое менялось во время выполнения цикла.
В настоящее время теги SVG не поддерживают динамическое добавление тегов, по крайней мере, в Chrome. Он даже не отображает новый прямоугольник, даже если вы просто добавили его непосредственно с помощью DOM или JQuery.append(). Взгляните, просто пытаясь сделать это с JQuery
// this doesn't even work right. Slightly different error than yours.
$(function() {
$('svg').append('<rect top="20" left="20" height="10" width="10" style="fill: #ff00ff" />');
});
Я предполагаю, что вы увидите действительно сумасшедшее поведение, независимо от того, что вы делаете таким образом. Похоже, что это проблема с браузером, а не столько Angular. Я бы порекомендовал сообщить об этом проекту Chromium.
РЕДАКТИРОВАТЬ:
Похоже, что это может сработать, если вы добавите его через DOM на 100% программным способом: как заставить Chrome перерисовывать SVG динамически добавляемый контент?
angular не обращает внимания на пространства имен при создании элементов (хотя элементы кажутся клонированными с пространством имен родителя), поэтому новые директивы внутри <svg>
не будет работать без специальной функции компиляции.
Что-то вроде следующего может обойти эту проблему, выполнив создание элементов dom. Чтобы это работало, вам нужно иметь xmlns="http://www.w3.org/2000/svg"
в качестве атрибута корневого элемента template
и ваш шаблон должен быть действительным XML (имеющим только один корневой элемент).
directiveGenerator = function($compile) {
var ret, template;
template = "<g xmlns=\"http://www.w3.org/2000/svg\"> +
"<rect top=\"20\" left=\"20\" height=\"10\" width=\"10\" style=\"fill: #ff00ff\" />" +
"</g>";
ret = {
restrict: 'E',
compile: function(elm, attrs, transclude) {
var templateElms;
templateElms = (new DOMParser).parseFromString(template, 'application/xml');
elm.replaceWith(templateElms.documentElement);
return function(scope, elm, attrs) {
// Your link function
};
}
};
return ret;
};
angular.module('svg.testrect', []).directive('testrec', directiveGenerator);