How to Lazy and Dynamically Load a Component in Angular

There are three ways you can use a component in an Angular app.

  • Loading on route change
  • Using as a child component
  • Dynamically loading on demand

However, this article focuses on loading a component dynamically and lazily. The main advantage of lazily loading a component is reducing the initial bundle size and only downloading the component in the browser when required.

Let us say that you have a component called GreetComponent, as shown in the next code block,

import { Component, EventEmitter, Input, Output } from '@angular/core';
import { CommonModule } from '@angular/common';

const template = `
   <h2>{{message}} </h2>
   <button (click)='sendMessage()'>Send Message </button>
`
@Component({
  selector: 'app-greet',
  standalone: true,
  imports: [CommonModule],
  template: template
})
export class GreetComponent {
  @Input({ required: true }) message?: string;
  @Output() messageEvent = new EventEmitter<boolean>();
  sendMessage(): void {
    this.messageEvent.emit(true);
  }
}

The GreetComponent has an @Input decorated property and an @Output() decorated EvenEmmiter. The FooComponent uses it as a child component, as shown in the following code block.

const template = `
   <app-greet [message]='message' (messageEvent)='sendMessage($event)'></app-greet>
`
@Component({
  selector: 'app-foo',
  standalone: true,
  imports: [CommonModule,
    GreetComponent],
  template : template
})
export class FooComponent {
   message = "data from parent"

   sendMessage(m:boolean){
      console.log(m);
   }
}

A couple of points worth noticing here are,

  • GreetComponent is part of the imports array.
  • GreetComponent is used on the template.

Due to the above two points, whenever Angular compiles FooComponent, it also includes GreetComponent, increasing the size of the bundle containing the FooComponent.

One main advantage of loading a component dynamically (lazily) is that it reduces the bundle size because it only gets downloaded in the browser when required.

Let us say that,  GreetComponent should be loaded dynamically and lazily with the click of a button in the FooComponent. For that, 

  • Add a button
  • Remove <app-greet> from the template of FooComponent
  • Remove GreetComponent from the imports array. [Important]
const template = `
   <button (click)='loadComponent()'>Load Greet Component </button>
`
@Component({
  selector: 'app-foo',
  standalone: true,
  imports: [CommonModule],
  template: template
})
export class FooComponent {
  message = "data from parent"
  greetcomp: any;

To dynamically load the component, inject the ViewContainerRef using the inject function or the constructor injection.  

  vcr = inject(ViewContainerRef);

After that, import the file that contains GreetComponent using the import statement.  The import statement is used in JavaScript to load a file dynamically.

const { GreetComponent } = await import('../greet/greet.component');

After importing the file, use the CreateComponent method of ViewContainerRef.

this.greetcomp = this.vcr.createComponent(GreetComponent);

You can access all properties and events of the dynamically loaded components using the instance method. So the a value can be passed to the message property as shown in the next code block,

this.greetcomp.instance.message = "Hello dynamic Component";

You can subscribe to the @Output decorated EventEmitter as shown next,

this.greetcomp.instance.messageEvent.subscribe((data:any)=>{
        console.log(data);
      })

Putting everything together, you can lazy load a GreetComponent with the click of a button in the FooComponent, as shown in the following code listing,

const template = `
   <button (click)='loadComponent()'>Load Greet Component </button>
`
@Component({
  selector: 'app-foo',
  standalone: true,
  imports: [CommonModule],
  template: template
})
export class FooComponent {
  message = "data from parent"
  greetcomp: any;
  vcr = inject(ViewContainerRef);
  
  async loadComponent() {
    this.vcr.clear();
    const { GreetComponent } = await import('../greet/greet.component');
    this.greetcomp = this.vcr.createComponent(GreetComponent);
    if (this.greetcomp) {
      this.greetcomp.instance.message = "Hello dynamic Component";

      this.greetcomp.instance.messageEvent.subscribe((data:any)=>{
        console.log(data);
      })
    }

  }
}

Currently, Angular loads GreetComponent at the end after all DOM elements of FooComponent. To load it at a specific div block, read the div block as ViewChild and ViewConatinerRef.  In the other post, I will cover that in detail.

I hope you find this post helpful. Thanks for reading.

Leave a comment

Create a website or blog at WordPress.com