Day 23 — Dynamic Components: Rendering at Runtime
📘 Day 23 — Dynamic Components: Rendering at Runtime
Zero to Hero — Hands-on Angular Tutorial
Today you will learn:
- ✔️ What are Dynamic Components?
- ✔️ Using
ViewContainerRef(The “Slot”) - ✔️
createComponentAPI - ✔️ Passing Data to Dynamic Components (
inputs) - ✔️ Loading components lazily (Code Splitting)
Normally, you use components in the template (<app-user></app-user>). But sometimes, you don’t know which component to load until runtime (e.g., a Modal system, Dashboard Widgets, or a CMS). 🧩
🟦 1. The Container (ViewContainerRef)
To create a component dynamically, we need a place to put it in the DOM.
We use an <ng-container> with a template variable (#container).
HTML (app.component.html):
<h1>Dynamic Dashboard</h1>
<button (click)="loadWidget()">Load Widget</button>
<div class="dashboard-grid">
<!-- This is where we will inject components -->
<ng-container #widgetContainer></ng-container>
</div>
🟩 2. Creating the Component
TS (app.component.ts):
import { Component, ViewChild, ViewContainerRef } from '@angular/core';
import { WidgetComponent } from './widget.component';
@Component({...})
export class AppComponent {
// 1. Get access to the container
@ViewChild('widgetContainer', { read: ViewContainerRef })
container!: ViewContainerRef;
loadWidget() {
// 2. Clear previous contents (optional)
this.container.clear();
// 3. Create the component!
const ref = this.container.createComponent(WidgetComponent);
// 4. Pass Data (Inputs)
ref.instance.title = 'Dynamic Title';
ref.setInput('data', { id: 123 }); // Modern Input setting
}
}
createComponent(): Instantiates the component class and inserts its HTML into the container.ref.instance: Access the actual class instance (to set properties).
🟧 3. Lazy Loading Dynamic Components
You don’t want to bundle every possible widget in your main bundle.
Use import() to load them only when needed.
async loadLazyWidget() {
this.container.clear();
// 1. Fetch the code chunk from the server
const { HeavyChartComponent } = await import('./heavy-chart/heavy-chart.component');
// 2. Render it
this.container.createComponent(HeavyChartComponent);
}
This is extremely powerful for building pluggable dashboards or admin panels.
🟥 4. Passing Data & Handling Outputs
Handling Inputs is easy (ref.instance.prop = val).
Handling Outputs (@Output) requires manual subscription.
const ref = this.container.createComponent(AlertComponent);
// Subscribe to the Output
const sub = ref.instance.closed.subscribe(() => {
console.log('Alert closed!');
ref.destroy(); // Remove component from DOM
sub.unsubscribe(); // Clean up memory
});
🟫 5. “ngComponentOutlet” (The Declarative Way)
If your logic is simple, you don’t need all that manual ViewContainerRef code.
Angular provides a directive for this.
HTML:
<ng-container *ngComponentOutlet="currentComponent"></ng-container>
TS:
currentComponent = WidgetComponent;
changeToChart() {
this.currentComponent = ChartComponent; // HTML updates automatically!
}
Passing Inputs (Angular 16+):
<ng-container *ngComponentOutlet="currentComponent; inputs: { title: 'Hello by Input' }"></ng-container>
🎉 End of Day 23 — What You Learned
Today you broke free from the static template:
- ✔️
ViewContainerRef: The anchor point for dynamic content. - ✔️
createComponent: The API to spawn components. - ✔️ Lazy Loading:
await import(...)to keep initial bundles small. - ✔️
ngComponentOutlet: The clean, declarative alternative.
🧪 Day 23 Challenge
Build a “Dynamic Modal Service”.
Requirements:
- Create
ModalService. - Methods:
open(componentType, data)andclose(). - The service should inject the component into the
body(or a specific root container). - When
open()is called withLoginFormComponent, it renders the Login Form in a popup. - When the user clicks “Close” inside the form, it destroys the component.
Hint: You might need ApplicationRef or store a global ViewContainerRef in AppComponent to start.