Blog>
Snippets

Lazy Loading Angular Elements

Detail the process of dynamically loading Angular Elements when they are needed, to improve initial load performance of web applications.

// app.module.ts
import { NgModule, Injector } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { createCustomElement } from '@angular/elements';
import { LazyElementDirective } from './lazy-element.directive'; // Directive for lazy loading

import { AppComponent } from './app.component';
//import { YourLazyComponent } from './your-lazy-component'; // Do not import lazy components here

@NgModule({
  declarations: [
    AppComponent,
    LazyElementDirective // Include the lazy loading directive
    // YourLazyComponent // Do not declare lazy components here
  ],
  imports: [BrowserModule],
  providers: [],
  bootstrap: [AppComponent],
  entryComponents: [ // YourLazyComponent will be added dynamically
    // YourLazyComponent
  ]
  // No need to export because they will be used as custom elements
})
export class AppModule { 

  constructor(private injector: Injector) {}

  ngDoBootstrap() {
    // Code to define lazy-loaded custom elements could be written here
  }

}
This Angular module is configured to allow for lazy loading of components. The LazyElementDirective is used to assist with loading elements lazily. Components that should be lazily loaded are not to be declared directly; instead, they will be added dynamically later in the ngDoBootstrap method or similar lifecycle hook.

// lazy-element.directive.ts
import { Directive, ViewContainerRef, Injector, ElementRef, Renderer2, AfterViewInit, Input } from '@angular/core';
import { NgModuleFactoryLoader, NgModuleRef, NgModule } from '@angular/core';

@Directive({ selector: '[appLazyElement]' })
export class LazyElementDirective implements AfterViewInit {

  @Input() appLazyElement: string; // Input for the module name to load

  constructor(
    private viewContainer: ViewContainerRef,
    private injector: Injector,
    private loader: NgModuleFactoryLoader
  ) {}

  async ngAfterViewInit() {
    this.loadElement(this.appLazyElement);
  }

  private async loadElement(moduleName: string) {
    const { moduleType, componentName } = await import(`./${moduleName}/${moduleName}.module`).then(m => m.getLazyModuleInfo());
    const moduleFactory = this.loader.load(moduleType);
    const moduleRef = moduleFactory.then((factory) => factory.create(this.injector)).then((mod: NgModuleRef<any>) => mod);
    const componentFactory = await moduleRef.then((mod) => mod.componentFactoryResolver.resolveComponentFactory(componentName));
    this.viewContainer.createComponent(componentFactory);
  }
}
This directive, when applied to an element, is responsible for lazy loading an Angular component. It uses NgModuleFactoryLoader to get a reference to the module that contains our lazy component. Once loaded, it then resolves the component factory and creates the component within the view container.

// your-lazy-module.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { YourLazyComponent } from './your-lazy-component.component';

@NgModule({
  declarations: [YourLazyComponent],
  imports: [CommonModule]
})
export class YourLazyModuleModule { 
  constructor() {}

  static getLazyModuleInfo() {
    return {
      moduleType: YourLazyModuleModule,
      componentName: YourLazyComponent
    };
  }
}
This is the lazy module containing the component to be loaded lazily. It exports a static method getLazyModuleInfo that returns an object containing the module and component to load, which is used by the LazyElementDirective to dynamically load the components.