Angular: Using ComponentFactoryResolver for dynamic instantiation of the components, rendering inside SVG

五迷三道 提交于 2020-12-24 23:50:05

问题


I have a component, which renders DOM, which is expected to be inside svg tag:

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;
}

When I instantiate it statically, everything works fine (the text is visible on the page):

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

The following DOM is generated:

<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>

The problem starts when I try to instantiate the component dynamically, using 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'
  }
}

The produced DOM seems looking OK, but for some reason, the text is not visible on the page:

<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>

Please see Reproduction at stackblitz


回答1:


I assume there are two questions here:

  1. How to make it work?
  2. Why is that happening?

The answer to the first question is using svg instead of g for grouping elements.
In your concrete example it would mean changing the selector:

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

And app.component.html:

<svg>
  <svg hello name="Static component"></svg>
</svg>
  • Working StackBlitz
  • Nested svg vs group

Now let's get to the second question. Why is this happening?
Your selector doesn't contain svg namespace. In order to render it correctly the selector should be svg:g[hello].
But that is impossible due to an old issue that's been there since Angular 5.
More details here and here.

As mentioned in this comment the main issue here is that Angular selector cannot contain namespace for creating element.
Selector svg:g[hello] will be parsed to g[hello], as a result Angular will use document.createElement instead of document.createElementNS to create new element.

Why using svg[hello] works?
Because if we use selector svg[hello] then it is parsed to <svg child> and for this tag Angular is providing namespace implicitly:

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



回答2:


Seems to be related to the old Angular issue: #10404, and also to the issue, mentioned by Vitalii: #20337

In #10404, DingWeizhe suggests the following work around:

Instead of this code:

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

To use this:

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

This change solves the problem without replacing <g> with <svg>. An accepted answer, of course, also solves the problem, but I have some concern about performance penalty, which could cause such replacement.

Working stackblitz is here




回答3:


Seems that it is related to the open issue, see

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



来源:https://stackoverflow.com/questions/56824484/angular-using-componentfactoryresolver-for-dynamic-instantiation-of-the-compone

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!