Skip to main content

Dynamic Components

Using ViewContainerRef

import { Component, viewChild, ViewContainerRef, inject } from '@angular/core';
import { UserComponent } from './user.component';

@Component({
selector: 'app-container',
standalone: true,
template: `
<button (click)="loadComponent()">Load Component</button>
<ng-container #container></ng-container>
`
})
export class ContainerComponent {
// Signal-based query - no lifecycle hook needed
container = viewChild.required('container', {
read: ViewContainerRef
});

loadComponent() {
const vcr = this.container(); // Access as signal
vcr.clear();
const componentRef = vcr.createComponent(UserComponent);

// Set inputs (for signal inputs, use .set())
componentRef.setInput('data', 'Dynamic data');

// Subscribe to outputs
componentRef.instance.dataEvent.subscribe((value) => {
console.log(value);
});
}
}

Dynamic Component with Data

import { Component, ComponentRef, viewChild, ViewContainerRef, Type } from '@angular/core';

@Component({
selector: 'app-dynamic-loader',
standalone: true,
template: `<ng-container #outlet></ng-container>`
})
export class DynamicLoaderComponent {
// Signal-based query
outlet = viewChild.required('outlet', {
read: ViewContainerRef
});

loadComponent(componentType: Type<any>, data: any) {
const vcr = this.outlet(); // Access as signal
const ref = vcr.createComponent(componentType);

// For signal inputs, use setInput()
Object.keys(data).forEach(key => {
ref.setInput(key, data[key]);
});

return ref;
}

clearComponents() {
this.outlet().clear(); // Access as signal
}
}

Portal (CDK)

import { Component, TemplateRef, viewChild } from '@angular/core';
import { CdkPortal, DomPortalOutlet } from '@angular/cdk/portal';

@Component({
selector: 'app-portal-example',
standalone: true,
imports: [CdkPortal],
template: `
<ng-template cdkPortal #portal>
<p>Portal content</p>
</ng-template>
`
})
export class PortalExampleComponent {
// Signal-based query
portal = viewChild.required<CdkPortal>('portal');

attachToBody() {
const outlet = new DomPortalOutlet(document.body);
outlet.attach(this.portal()); // Access as signal
}
}

Dynamic Dialog Example

@Injectable({ providedIn: 'root' })
export class DialogService {
private vcr = inject(ViewContainerRef);

open(component: Type<any>, data?: any): ComponentRef<any> {
const ref = this.vcr.createComponent(component);
if (data) {
Object.assign(ref.instance, data);
}
return ref;
}

close(ref: ComponentRef<any>) {
ref.destroy();
}
}

Lazy Load Component

async loadLazyComponent() {
const { LazyComponent } = await import('./lazy.component');
this.container.createComponent(LazyComponent);
}

Best Practices

  • Clear components when done: componentRef.destroy()
  • Use CDK Portal for complex scenarios
  • Prefer declarative approaches (*ngIf, *ngFor) when possible
  • Use for: modals, tooltips, dynamic forms, plugin systems