Angular: использование ComponentFactoryResolver для динамической реализации компонентов, рендеринга внутри SVG - PullRequest
7 голосов
/ 30 июня 2019

У меня есть компонент, который отображает 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>

Пожалуйста, смотрите Воспроизведение при stackblitz

Ответы [ 3 ]

7 голосов
/ 30 июня 2019

Я предполагаю, что здесь есть два вопроса:

  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.
Подробнее здесь и здесь .

Как уже упоминалось в этом комментарии, основная проблема заключается в том, что угловой селектор не может содержать пространство имен для создания элемента.
Селектор svg:g[hello] будет проанализирован на g[hello], в результате Angular будет использовать document.createElement вместо document.createElementNS для создания нового элемента.

Почему использование svg[hello] работает?
Потому что если мы используем селектор svg[hello], то он анализируется на <svg child>, и для этого тега Angular неявно предоставляет пространство имен :

'svg': new HtmlTagDefinition({implicitNamespacePrefix: 'svg'}),
2 голосов
/ 30 июня 2019

Похоже, что это связано со старой угловой проблемой: # 10404 , а также с проблемой, упомянутой Виталием: # 20337

В # 10404 , DingWeizhe предлагает следующую работу:

Вместо этого кода:

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

Для использованияthis:

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

Это изменение решает проблему без замены <g> на <svg>.Конечно, принятый ответ также решает проблему, но у меня есть некоторые опасения по поводу снижения производительности, которое может привести к такой замене.

2 голосов
/ 30 июня 2019

Похоже, что это связано с открытым вопросом, см.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...