角度:使用ComponentFactoryResolver进行组件的动态实例化,在SVG内部渲染

时间:2019-06-30 11:26:58

标签: html angular svg

我有一个呈现DOM的组件,该组件应位于svg标签内:

import { Component, Input } from '@angular/core';

@Component({
  selector: 'g[hello]',
  template: `<svg:text x="50%" y="50%" text-anchor="middle">Hello, {{name}}</svg:text>`,
  styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent  {
  @Input() name: string;
}

当我静态实例化它时,一切正常(文本在页面上可见):

<svg>
  <svg:g hello name="Static component"></svg:g>
</svg>

将生成以下DOM:

<svg _ngcontent-iej-c129="">
  <g _ngcontent-iej-c129="" hello="" name="Static component" _nghost-iej-c130="" ng-reflect-name="Static component">
    <text _ngcontent-iej-c130="" text-anchor="middle" x="50%" y="50%">
      Hello, Static component
    </text>
  </g>
</svg>

当我尝试使用ComponentFactoryResolver动态实例化组件时,问题就开始了:

<svg>
  <ng-container #container></ng-container>
</svg>
import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, OnInit } from '@angular/core';
import { HelloComponent } from './hello.component'

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent implements OnInit {
  @ViewChild('container', {read: ViewContainerRef, static: true}) container: ViewContainerRef;

  constructor(private componentFactoryResolver: ComponentFactoryResolver) {

  }

  ngOnInit() {
    // Instantiating HelloComponent dynamically
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(HelloComponent)
    const componentRef = this.container.createComponent(componentFactory);
    componentRef.instance.name = 'Dynamic component'
  }
}

产生的DOM看起来不错,但由于某些原因,文本在页面上不可见:

<svg _ngcontent-iej-c129="">
  <!---->
  <g hello="" _nghost-iej-c130="">
    <text _ngcontent-iej-c130="" text-anchor="middle" x="50%" y="50%">
      Hello, Dynamic component
    </text>
  </g>
</svg>

请参阅Reproduction at stackblitz

3 个答案:

答案 0 :(得分:7)

我认为这里有两个问题:

  1. 如何使其工作?
  2. 为什么会这样?

第一个问题的答案是使用svg而不是g对元素进行分组。
在您的具体示例中,这意味着更改选择器:

@Component({
  selector: 'svg[hello]',
  template: `<svg:text x="50%" y="50%" text-anchor="middle">Hello, {{name}}</svg:text>`,
  styles: [`h1 { font-family: Lato; }`]
})

还有app.component.html

<svg>
  <svg hello name="Static component"></svg>
</svg>

现在让我们来看第二个问题。为什么会这样?
您的选择器不包含svg命名空间。为了正确呈现它,选择器应为svg:g[hello]
但这是不可能的,因为自Angular 5起就存在一个古老的问题。
herehere的更多详细信息。

this评论中所述,这里的主要问题是Angular选择器不能包含用于创建元素的名称空间。
选择器svg:g[hello]将被解析为g[hello],因此Angular将使用document.createElement而不是document.createElementNS来创建新元素。

为什么使用svg[hello]有用?
因为如果我们使用选择器svg[hello],它将被解析为<svg child>,对于此标记,Angular为providing namespace implicitly

'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),

答案 1 :(得分:2)

似乎与未解决的问题有关,请参见

https://github.com/angular/angular/issues/20337

答案 2 :(得分:2)

似乎与旧的Angular问题有关:#10404,也与Vitalii提到的问题有关:#20337

#10404中,DingWeizhe提出了以下解决方法:

代替此代码:

    const componentRef = this.container.createComponent(componentFactory);

要使用此功能:

    const groupElement = document.createElementNS("http://www.w3.org/2000/svg", "g");
    const componentRef = componentFactory.create(injector, [], groupElement);
    this.container.insert(componentRef.hostView)

此更改可解决问题,而无需将<g>替换为<svg>。可以接受的答案当然也可以解决问题,但是我担心性能下降会导致这种情况。

正在工作的堆叠闪电是here