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
This answer by @Magicaner helped me but is missing a few details to get it working.
In summary, yes you can add a new child component using Renderer2.appendChild
. The directive would look like this:
@Directive({
selector: '[myDynamicDirective]'
})
export class MyDynamicDirective implements OnInit {
constructor(
private cfResolver: ComponentFactoryResolver,
public vcRef: ViewContainerRef,
private renderer: Renderer2
) { }
ngOnInit() {
this.appendComponent();
}
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
);
}
}
It adds a new instance of MyDynamicComponent
as a child. To get the elRef
from that dynamic component to send to appendChild
you can do this in the component definition:
@Component({
selector: 'my-dynamic-component',
template: '...'
})
export class MyDynamicComponent {
constructor(public elRef: ElementRef) { }
}
And also you need to add the dynamic component to entryComponents
in the module, and of course add the directive and dynamic component to the module declarations
.
Working stackblitz here
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);
}
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
);
}
}
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)
}
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.
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);
}