繁体   English   中英

在Angularjs指令中包含SVG模板

[英]Including SVG template in Angularjs directive

<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,如果实际上是解决方案。

有一个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的答案为例: 在Angularjs指令中包含SVG模板


老答案

如果将来遇到这种情况,我可以创建一个与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
  };
});

目前,SVG标记不支持动态添加标记,至少在Chrome中是这样。 它甚至不显示新的rect,即使你直接用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动态添加内容?

使用innerHTML shim for SVG(又名“innerSVG”)提供了另一个这个难题,作为Jason More的深刻回答的替代品。 我觉得这个innerHTML / innerSVG方法更优雅,因为它不需要像Jason的答案那样的特殊编译函数,但它至少有一个缺点:如果在指令上设置了“replace:true”,它就不起作用。 (这里讨论了innerSVG方法: 在SVG / XML中是否有一些innerHTML替换?

// 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()实例化(如在Jason的回答中),两者都可以适当地设置名称空间。 如果这个使用SVG的过程像HTML一样容易,那就太好了,但不幸的是它们的DOM结构略有不同,导致需要提供我们自己的innerHTML垫片。 现在,如果我们也可以使用“replace:true”来处理这个问题,那将是件好事!

编辑:更新了appendChild()循环以解决在循环运行时更改内容的问题。

angular在创建元素时不会注意名称空间(尽管元素似乎是用父项的namepsace克隆的),因此如果没有自定义编译函数, <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);

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM