Angular2: вставка динамического компонента в качестве дочернего элемента контейнера в DOM

Есть ли способ динамически вставить компонент в видеребенок (не родственник) тега DOM в Angular 2?

Существует множество примеров того, как вставить динамический компонент в качестве родственного элемента данногоViewContainerRefТег, как (на RC3):

@Component({
  selector: '...',
  template: '<div #placeholder></div>'
})
export class SomeComponent {
  @ViewChild('placeholder', {read: ViewContainerRef}) placeholder;

  constructor(private componentResolver: ComponentResolver) {}

  ngAfterViewInit() {
    this.componentResolver.resolveComponent(MyDynamicComponent).then((factory) => {
        this.componentRef = this.placeholder.createComponent(factory);
    });
  }
}

Но это создает DOM, похожий на:

<div></div>
<my-dynamic-component></my-dynamic-component>

Ожидаемый результат:

<div>
    <my-dynamic-component></my-dynamic-component>
</div>

С использованиемSomeComponent«sViewContainerRef имеет тот же результат, он по-прежнему вставляет сгенерированный компонент как родственный, а не дочерний элемент. Я был бы в порядке с решением, где шаблон пуст и динамические компоненты вставлены в шаблон (в теге выбора компонента).

Структура DOM очень важна при использовании таких библиотек, как ng2-dragula, для перетаскивания из списка динамических компонентов ивоспользоваться обновлениями модели, Дополнительный элемент div находится в списке перетаскиваемых элементов, но за пределами модели, что нарушает логику перетаскивания.

Некоторые говорят, что это невозможно (c.f. этот комментарий), но это звучит как очень удивительное ограничение.

 Günter Zöchbauer29 июн. 2016 г., 10:06
 Antoine29 июн. 2016 г., 11:17
Спасибо за быстрый ответ и полезную ссылку! @pkozlowski дал ответ на мой вопрос по этому обсуждению: использовать<template #placeholder></template> вместо<div #placeholder></div>.
 AngularOne23 апр. 2017 г., 17:12
У вас есть решение, которое будет работать и по директиве? так как это для компонента

Ответы на вопрос(4)

к Renderer2.

  constructor(
    private el: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
    private render: Renderer2
  ) {}

  ngOnInit() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
      MyDynamicComponent,
    );
    const componentRef = this.viewContainerRef.createComponent(componentFactory);
    this.render.appendChild(this.el.nativeElement, componentRef.location.nativeElement)
  }
 Marc J. Schmidt17 мая 2019 г., 20:17
Это распадается, если на вашем хосте есть * ngIf или другая директива, которая преобразует его в шаблон.
 Maurits Moeys12 авг. 2018 г., 14:21
Настройка структуры DOM с использованием рендерера может привести к проблемам с обнаружением внутренних изменений, см.этот блог, Я не знаю, вызовет ли это проблемы в этом конкретном случае, хотя ...
Решение Вопроса

TL; DR: заменить<div #placeholder></div> с<div><ng-template #placeholder></ng-template></div> вставить внутриdiv.

Вотпример рабочего стекаблица (Угловой 6) и соответствующий код:

@Component({
  selector: 'my-app',
  template: `<div><ng-template #container></ng-template></div>`
})
export class AppComponent implements OnInit {

    @ViewChild('container', {read: ViewContainerRef}) viewContainer: ViewContainerRef;

    constructor(private compiler: Compiler) {}

    ngOnInit() {
      this.createComponentFactory(MyDynamicComponent).then(
        (factory: ComponentFactory<MyDynamicComponent>) => this.viewContainer.createComponent(factory),
        (err: any) => console.error(err));
    }

    private createComponentFactory(/*...*/) {/*...*/}

}

Похоже на то<ng-container #placeholder></ng-container> также работает (заменитьng-template отng-container). Мне нравится этот подход, потому что<ng-container> четко адресовано этому сценарию использования (контейнеру, который не добавляет тег) и может использоваться в других ситуациях, таких какNgIf без упаковки в реальный тег.

PS: @ GünterZöchbauer направил меня к правильному обсуждению в комментарии, и я наконец-то ответил на свой вопрос.

Редактировать [2018-05-30]: обновлена ​​ссылка на stackblitz, чтобы иметь работающий, обновленный пример.

 Antoine30 мая 2018 г., 20:30
@BenCheng ng-template / ng-component фактически исчезает в отображаемом HTML. Это просто вид тега, указывающий положение, в которое вы хотите вставить свой компонент. Вы можете проверить обновленный пример и проверить его с помощью инструментов разработчика браузера, чтобы подтвердить это.
 Antoine06 июн. 2018 г., 00:02
@BenCheng Этот подход работает начиная с Angular 2 (или, по крайней мере, 4 с одинаковым синтаксисом), поэтому может быть другая причина, по которой вы получаете другое поведение. Может быть, это может быть хорошим отдельным вопросом :)
 Karthick21 окт. 2016 г., 21:32
Заполнитель не известен, который является динамическим. Можно ли динамически создавать ViewChild.?
 Ben Cheng31 мая 2018 г., 03:53
@Antoine. На самом деле, он занимал место для этого, но не рендеринг.
 Antoine02 нояб. 2016 г., 10:59
@Kartick извините за задержку. Я считаю, что возможно получить полностью динамический ViewChild, но я не знаю, как это сделать. У ViewChild есть разные способы идентифицировать компонент, на который нужно нацелиться. Это может быть отдельный вопрос, чтобы задать :)
 Myonara23 окт. 2018 г., 19:51
@ Антуан Великолепно. Мне нравится простота этого. Спасибо большое
 Ben Cheng24 мая 2018 г., 17:29
Но положение динамического компонента будет <my-app> <div> <ng-template #container> </ ng-template> </ div> <! - Положение динамического компонента -> </ my-app>. Может ли ng-template исчезнуть?
 Antoine22 окт. 2018 г., 22:33
Единственный известный мне подход состоит в том, чтобы получить экземпляр компонента в конце и вручную поставить такие значения, какmyCompInstance.foo = 'Hello world!', Кстати, я думаю, что Angular делает что-то вроде этого (потому что @Input не доступны в конструкторе, только из хука NgOnInit). Я обновил пример stackblitz, чтобы также установить свойство компонента.
 Ben Cheng05 июн. 2018 г., 19:23
@Antoine. Да. Пример в stackblitz работает. Я обнаружил, что я использую угловой 5. Я думаю, это может быть основной причиной.
 Myonara22 окт. 2018 г., 19:53
Хороший вопрос и отличный ответ. Однако есть ли способ передать параметры этому динамическому компоненту? Только для инициализации и идентификации компонента, остальное можно сделать через общий сервис ...
 Antoine05 июн. 2018 г., 18:24
@BenCheng В ссылке в моем ответе (stackblitz) я не получаю тот же вывод, что вы описываете. Шаблон компонента<h1><ng-template #container></ng-template></h1> и это оказывает<h1><!----><my-dynamic>I am inserted dynamically!</my-dynamic></h1>, Динамический компонент находится там, где я ожидал, а не за пределами<h1> а также<ng-template> исчез (заменен пустым комментарием, я думаю). Что-то не так?

и одобренный ответ не работал для меня. Но я нашел лучшее и более логичное решение, как добавить динамически созданный компонент как дочерний элемент к текущему элементу хоста.

Идея состоит в том, чтобы использовать ссылку на элемент только что созданного компонента и добавить ее к текущему элементу ref с помощью службы визуализации. Мы можем получить компонент объекта через его свойство инжектора.

Вот код:

@Directive({
    selector: '[mydirective]'
})
export class MyDirectiveDirective {
    constructor(
        private cfResolver: ComponentFactoryResolver,
        public vcRef: ViewContainerRef,
        private renderer: Renderer2
    ) {}

    public appendComponent() {
        const factory = 
        this.cfResolver.resolveComponentFactory(MyDynamicComponent);
        const componentRef = this.vcRef.createComponent(factory);
        this.renderer.appendChild(
           this.vcRef.element.nativeElement,
           componentRef.injector.get(MyDynamicComponent).elRef.nativeElement
        );
    }
}
 et3rnal07 мая 2019 г., 09:01
Не могли бы вы добавить стекблиц? Я не могу заставить его работать?

просто сохраните ссылку на динамически созданный компонент (родного брата) и переместите его, используя vanilla JS:

  constructor(
    private el: ElementRef,
    private viewContainerRef: ViewContainerRef,
    private componentFactoryResolver: ComponentFactoryResolver,
  ) {}

  ngOnInit() {
    const componentFactory = this.componentFactoryResolver.resolveComponentFactory(
      MyDynamicComponent,
    );
    const componentRef = this.viewContainerRef.createComponent(componentFactory);
    this.el.nativeElement.appendChild(componentRef.location.nativeElement);
  }
 Marc J. Schmidt17 мая 2019 г., 20:16
Это распадается, если на вашем хосте есть * ngIf или другая директива, которая преобразует его в шаблон.

Ваш ответ на вопрос