AngularT2_Light_1200x303

This article explains various ways of lazy loading a component in Angular, including via an import statement inside an async-await function and via the then method.

The previous major version of Angular, 9, came with a lot of amazing features, and one of the most talked-about among them is the Ivy renderer. Ivy is a new compiler of Angular. It opens up a lot of new features and also provides a simpler API to achieve specific tasks such as lazy (dynamically) loading a component.

This article explains a step-by-step approach to lazy load a component and provides the code along the way.

Create the Lazy-Loaded Component

In the application, add a new component using the Angular CLI command, as shown below.

ng g c greet --flat --skip-import

Here --skip-import flag is used so that Angular does not declare GreetComponent in the module, as you wish to load GreetComponent dynamically.

Next, add code in GreetComponent as shown in the next code listing:

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

@Component({
  selector: 'app-greet',
  template: `
  <h1>Greetings </h1>
  <h2>{{greetMessage}}</h2>
  <button (click)='greet()'>sayHello</button>
  `

})
export class GreetComponent implements OnInit {

  @Input() greetMessage: string;
  @Output() sendMessageEvent = new EventEmitter();
    
  constructor() { }
  ngOnInit(): void {
  }

  greet(): void {
    this.sendMessageEvent.emit('Hello From Greet Component');

  }
}

GreetComponent has an @Input() decorated property to accept data from the parent component and an @Output() decorated EventEmitter so that the event raised here can be handled in the parent component. The parent component is a component in which GreetComponent will be loaded dynamically.

Lazy Load in the Parent Component

You can lazily load a component in any other component, hence creating a parent-child relationship between them. You want to lazy load GreetComponent on the click of the button in the parent component, so to do that add a button as shown next.

<h1>{{message}}</h1>
<button (click)='loadGreetComponent()'>Greet</button>

Next, in the constructor of the parent component inject ViewContainerRef and ComponentFactoryResolver classes:

  constructor(private vcref: ViewContainerRef,
    private cfr: ComponentFactoryResolver){ }

As of the official documentation, ComponentFactoryResolver class is a simple registry that maps components to generated ComponentFactory classes that can be used to create an instance of the component using the create() method.

The ViewContainerRef represents a container where one or more views can be attached. It can contain host views by instantiating a component with the createComponent() method.

To lazy load the component, we will use the import() method inside an async/await function.

async loadGreetComponent(){
    
    this.vcref.clear();
    const { GreetComponent } = await import('./greet.component');
    let greetcomp = this.vcref.createComponent(
      this.cfr.resolveComponentFactory(GreetComponent)
    );
  
  }

The above function first clears the container; otherwise, on every click of the button, the new instance of GreetComponent would be added in the container. After that, the function imports the GreetComponent using the import method. By using await syntax, it loads the component asynchronously. Once the reference of GreetComponent is loaded, it creates the component on the view container using the createComponent method and bypassing resolveComponentFactory method in it.

Now when you click the button, you will lazy load the GreetComponent inside the parent component.

Passing Data to Lazy-Loaded Component

Angular allows us to pass data to @Input() decorated properties and handle events of lazy-loaded components using the instance property of the lazy-loaded component. For example, you can pass data and handle an event of GreetComponent in the parent component as shown in the next code listing:

  greetcomp.instance.greetMessage = "Data Passed from Parent";
    greetcomp.instance.sendMessageEvent.subscribe(data=>{
      console.log(data);
    })

As you see, you can use instance to access the properties and events of the lazy-loaded component.

Using then() Instead of await

Sometimes it is not possible to make a function async. Hence, you cannot use an await statement as we did earlier. In this scenario you can use promise’s then method to lazy load a component as shown in the next code listing:

  loadGreetComponent() {
    this.vcref.clear();
    import('./greet.component').then(
      ({ GreetComponent }) => {
        let greetcomp = this.vcref.createComponent(
          this.cfr.resolveComponentFactory(GreetComponent)
        );
        greetcomp.instance.greetMessage = "Data Passed from Parent";
        greetcomp.instance.sendMessageEvent.subscribe(data => {
          console.log(data);
        })
      }
    )
  }

In the above function, everything is the same. It just promises the then method is used instead of async-await statements.

Using a Module in the Lazy-Loaded Component

Sometimes a lazy-loaded component relies on other modules. For example, let’s say GreetComponent is using [(ngModel)] as shown in the next code listing:

 template: `
  <h1>Greetings </h1>
  <h2>{{greetMessage}}</h2>
  <input type='text' [(ngModel)]='message' />
  <h3>Hello {{message}}</h3>
  <button (click)='greet()'>sayHello</button>
  `

As ngModel is the part of FormsModule, when you use it inside a lazy-loaded component, Angular complains about that with error: Can’t bind to ngModel since it isn’t a known property of input.

This problem can be fixed by importing FormsModule inside the GreetComponent itself. With that inside the same file greet.component.ts, create a module and pass FormsModule inside the imports array as shown in the next code listing:

@NgModule({
  declarations: [GreetComponent],
  imports: [FormsModule]
})
class PlanetComponentModule {}

You have to create ngModule in the same file in which the component is created and pass all the dependent modules in the imports array.

Lazy Loading Components in ng-template

To lazy load a component inside a particular location in the template, you can use ViewChild. Let’s say you wish to lazy load GreetComponent inside the #greettemp template of the parent component.

<div>
    <ng-template #greettemp></ng-template>
</div>

To do this, refer greettemp as a ViewChild in the parent component class as shown in the next code listing:

  @ViewChild('greettemp', { read: ViewContainerRef })
  private greetviewcontainerref: ViewContainerRef;

Here we are reading the ng-template as ViewContainerRef so that the component can be loaded to it, and finally, you can lazily load a component inside it as we did earlier:

async loadGreetComponent(){

    this.vcref.clear();
    const { GreetComponent } = await import('./greet.component');
    let greetcomp = this.greetviewcontainerref.createComponent(
      this.cfr.resolveComponentFactory(GreetComponent)
    );
    greetcomp.instance.greetMessage = "Data dhdhdh from Parent";
    greetcomp.instance.sendMessageEvent.subscribe(data=>{
      console.log(data);
    })

  }

Summary

This article explained how you can use an import statement inside an async-await function to lazy load a component in Angular. I hope you found the article useful. Thanks for reading it.


Dhananjay Kumar
About the Author

Dhananjay Kumar

Dhananjay Kumar is an independent trainer and consultant from India. He is a published author, a well-known speaker, a Google Developer Expert, and a 10-time winner of the Microsoft MVP Award. He is the founder of geek97, which trains developers on various technologies so that they can be job-ready, and organizes India's largest Angular Conference, ng-India. He is the author of the best-selling book on Angular, Angular Essential. Find him on Twitter or GitHub.

Related Posts

Comments

Comments are disabled in preview mode.