#angular #dynamic-components #viewcontainerref #runtime

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”)
  • ✔️ createComponent API
  • ✔️ 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:

  1. Create ModalService.
  2. Methods: open(componentType, data) and close().
  3. The service should inject the component into the body (or a specific root container).
  4. When open() is called with LoginFormComponent, it renders the Login Form in a popup.
  5. 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.