【问题标题】:Angular: Using ComponentFactoryResolver for dynamic instantiation of the components, rendering inside SVGAngular:使用 ComponentFactoryResolver 进行组件的动态实例化,在 SVG 中渲染
【发布时间】:2019-11-11 10:47:56
【问题描述】:

我有一个渲染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

【问题讨论】:

    标签: html angular svg


    【解决方案1】:

    我假设这里有两个问题:

    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],那么它会被解析为&lt;svg child&gt;,对于这个标签,Angular 是providing namespace implicitly

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

    【讨论】:

      【解决方案2】:

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

      #10404DingWeizhe 建议以下解决方法:

      代替此代码:

          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)
      

      此更改解决了问题,无需将&lt;g&gt; 替换为&lt;svg&gt;。当然,接受的答案也可以解决问题,但我对性能损失有些担心,这可能会导致这种替换。

      工作堆栈闪电是here

      【讨论】:

      • 我在 github issue 中留了个便条。
      【解决方案3】:

      好像和open issue有关,见

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

      【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-02-28
      • 2018-12-29
      • 2021-09-11
      • 1970-01-01
      • 1970-01-01
      • 2018-02-06
      • 2016-08-04
      • 1970-01-01
      相关资源
      最近更新 更多