Deep Dive into Dynamic Components in Angular

Introduction

Dynamic components in Angular refer to components that are created and inserted into the view at runtime, rather than being statically defined in the template. This allows for greater flexibility in building applications where the components to be displayed can change based on user interaction, data received from an API, or other runtime conditions.

Use Cases for Dynamic Components

  1. Dialogs and Modals: Dynamically create and insert dialog or modal components based on user actions.
  2. Dynamic Forms: Generate form fields dynamically based on form configuration data.
  3. Component-Based UI Customization: Allow users to customize the UI by dynamically adding or removing components.
  4. Lazy Loading Components: Improve performance by loading components only when needed.

Creating and Using Dynamic Components

To create and use dynamic components in Angular, you need to follow these steps:

  1. Create the Component:

    • Define the component that will be created dynamically.
    import { Component } from '@angular/core';
     
    @Component({
      selector: 'app-dynamic',
      template: `<div>Dynamic Component Loaded!</div>`,
      styles: [`div { color: blue; }`]
    })
    export class DynamicComponent {}
  2. Prepare the Host View:

    • Define a directive to mark the location where the dynamic component will be inserted.
    import { Directive, ViewContainerRef } from '@angular/core';
     
    @Directive({
      selector: '[appDynamicHost]'
    })
    export class DynamicHostDirective {
      constructor(public viewContainerRef: ViewContainerRef) {}
    }
  3. Load the Dynamic Component:

    • Use ComponentFactoryResolver and ViewContainerRef to create and insert the dynamic component.
    import { Component, ComponentFactoryResolver, ViewChild } from '@angular/core';
    import { DynamicHostDirective } from './dynamic-host.directive';
    import { DynamicComponent } from './dynamic.component';
     
    @Component({
      selector: 'app-dynamic-loader',
      template: `<ng-template appDynamicHost></ng-template>`
    })
    export class DynamicLoaderComponent {
      @ViewChild(DynamicHostDirective, { static: true }) dynamicHost!: DynamicHostDirective;
     
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
     
      loadDynamicComponent() {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
        const viewContainerRef = this.dynamicHost.viewContainerRef;
        viewContainerRef.clear();
        viewContainerRef.createComponent(componentFactory);
      }
    }
    • Usage in a template:
      <button (click)="loadDynamicComponent()">Load Dynamic Component</button>
      <ng-template appDynamicHost></ng-template>

Advanced Usage of Dynamic Components

  1. Passing Data to Dynamic Components:

    • You can pass data to the dynamically created component by setting its properties.
    import { Component, ComponentFactoryResolver, ViewChild, Input } from '@angular/core';
    import { DynamicHostDirective } from './dynamic-host.directive';
    import { DynamicComponent } from './dynamic.component';
     
    @Component({
      selector: 'app-dynamic-loader',
      template: `<ng-template appDynamicHost></ng-template>`
    })
    export class DynamicLoaderComponent {
      @ViewChild(DynamicHostDirective, { static: true }) dynamicHost!: DynamicHostDirective;
     
      constructor(private componentFactoryResolver: ComponentFactoryResolver) {}
     
      loadDynamicComponent(data: any) {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
        const viewContainerRef = this.dynamicHost.viewContainerRef;
        viewContainerRef.clear();
        const componentRef = viewContainerRef.createComponent(componentFactory);
        componentRef.instance.data = data;
      }
    }
    • Dynamic Component:
      import { Component, Input } from '@angular/core';
       
      @Component({
        selector: 'app-dynamic',
        template: `<div>Dynamic Component Loaded! Data: {{ data }}</div>`,
        styles: [`div { color: blue; }`]
      })
      export class DynamicComponent {
        @Input() data: any;
      }
  2. Destroying Dynamic Components:

    • It’s important to manage the lifecycle of dynamic components. You can destroy a dynamic component by calling the destroy method on the component reference.
    loadDynamicComponent() {
      const componentFactory = this.componentFactoryResolver.resolveComponentFactory(DynamicComponent);
      const viewContainerRef = this.dynamicHost.viewContainerRef;
      viewContainerRef.clear();
      const componentRef = viewContainerRef.createComponent(componentFactory);
      componentRef.instance.data = data;
     
      // Destroy the component after 5 seconds
      setTimeout(() => componentRef.destroy(), 5000);
    }
  3. Using Dynamic Components in a Service:

    • You can encapsulate the logic for creating dynamic components within a service for better reusability and separation of concerns.
    import { Injectable, ComponentFactoryResolver, ApplicationRef, Injector, EmbeddedViewRef } from '@angular/core';
     
    @Injectable({ providedIn: 'root' })
    export class DynamicComponentService {
      constructor(
        private componentFactoryResolver: ComponentFactoryResolver,
        private appRef: ApplicationRef,
        private injector: Injector
      ) {}
     
      createComponent(component: any, data: any) {
        const componentFactory = this.componentFactoryResolver.resolveComponentFactory(component);
        const componentRef = componentFactory.create(this.injector);
     
        componentRef.instance.data = data;
     
        this.appRef.attachView(componentRef.hostView);
        const domElem = (componentRef.hostView as EmbeddedViewRef<any>).rootNodes[0] as HTMLElement;
        document.body.appendChild(domElem);
     
        return componentRef;
      }
     
      destroyComponent(componentRef: any) {
        this.appRef.detachView(componentRef.hostView);
        componentRef.destroy();
      }
    }
    • Usage in a component:
      import { Component } from '@angular/core';
      import { DynamicComponentService } from './dynamic-component.service';
      import { DynamicComponent } from './dynamic.component';
       
      @Component({
        selector: 'app-dynamic-loader',
        template: `<button (click)="loadComponent()">Load Dynamic Component</button>`
      })
      export class DynamicLoaderComponent {
        constructor(private dynamicComponentService: DynamicComponentService) {}
       
        loadComponent() {
          const componentRef = this.dynamicComponentService.createComponent(DynamicComponent, { message: 'Hello, World!' });
       
          // Destroy the component after 5 seconds
          setTimeout(() => this.dynamicComponentService.destroyComponent(componentRef), 5000);
        }
      }

Summary

Dynamic components in Angular provide a powerful way to create and insert components at runtime, allowing for flexible and dynamic user interfaces. By leveraging ComponentFactoryResolver, ViewContainerRef, and other Angular services, developers can load, configure, and manage the lifecycle of dynamic components effectively. This capability is particularly useful in scenarios such as dialogs, dynamic forms, and component-based UI customization. Understanding and using dynamic components can significantly enhance the interactivity and responsiveness of Angular applications.