Angular2: Insert a dynamic component as child of a container in the DOM

廉价感情. 提交于 2019-11-26 17:37:14

问题


Is there a way to insert dynamically a component as a child (not a sibling) of a DOM tag in Angular 2?

There are plenty of examples around there to insert a dynamic component as a sibling of a given ViewContainerRef's tag, like (as of 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);
    });
  }
}

But this generates a DOM similar to:

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

Expected result:

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

Using the SomeComponent's ViewContainerRef has the same result, it is still inserting the generated component as a sibling, not a child. I would be okay with a solution where the template is empty and dynamic components are inserted in the template (within the component selector tag).

The DOM structure is very important when using libraries like ng2-dragula to drag from a list of dynamic components and benefit from the model updates. The extra div is in the list of draggable elements, but outside the model, breaking the drag & drop logic.

Some say it is not possible (c.f. this comment), but it sounds like a very surprising limitation.


回答1:


TL;DR: replace <div #placeholder></div> with <div><ng-template #placeholder></ng-template></div> to insert inside the div.

Here is a working stackblitz example (Angular 6), and the relevant code:

@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(/*...*/) {/*...*/}

}

It seems <ng-container #placeholder></ng-container> is also working (replace ng-template by ng-container). I like this approach because <ng-container> is clearly addressed to this usecase (a container that don't add a tag) and can be used in other situations like NgIf without wrapping in a real tag.


PS: @GünterZöchbauer directed me to the right discussion in a comment, and I finally answered my own question.


Edit [2018-05-30]: Updated to stackblitz link to have a working, up-to-date example.




回答2:


I was searching for solution for same problem and approved answer not worked for me. But I have found better and much logical solution how to append dynamically created component as child to current host element.

The idea is to use element reference of newly created component and append it to current element ref by using render service. We can get component object via its injector property.

Here is the code:

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



回答3:


My dirty way, just save the reference of dynamically created component (sibling) and move it using 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);
  }



回答4:


My Solution would be quite similar to @Bohdan Khodakivskyi. But I tried to the 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)
  }



回答5:


Easier approache is now available with Portal available in @angular/cdk.

npm i @angular/cdk

app.module.ts:

import { PortalModule } from '@angular/cdk/portal';

@NgModule({
  imports: [PortalModule],
  entryComponents: [MyDynamicComponent]
})

SomeComponent:

@Component({
  selector: 'some-component',
  template: `<ng-template [cdkPortalOutlet]="myPortal"></ng-template>`
})
export class SomeComponent {
  ...
  this.myPortal = new ComponentPortal(MyDynamicComponent);
}


来源:https://stackoverflow.com/questions/38093727/angular2-insert-a-dynamic-component-as-child-of-a-container-in-the-dom

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