Ich möchte euch in diesem Beitrag zeigen, wie es mit Angular möglich ist Module manuell nachzuladen. Doch zuerst möchte ich auf Lazy Loading im allgemeinen eingehen und wie es in Angular normalerweise genutzt wird.

Warum Lazy Loading?

Gerade bei größeren Anwendungen ist es nötig, Teile der Anwendung erst zu laden, wenn diese tatsächlich benötigt werden. Ansonsten besteht das Problem, dass die gesamte Anwendung zuerst vollständig geladen werden muss und erst danach zu sehen ist. Je nach Größe der Anwendung kommen da schon einige Megabyte zusammen und das bedeutet Einbußen bei der Performance.

Die Lösung für dieses Problem lautet “Lazy Loading”. Dadurch werden initial nur die nötigsten Daten geladen und die restlichen Teile der Anwendung erst dann, wenn diese auch tatsächlich benötigt werden.

Lazy Loading via Angular Router

In Angular wird das Problem gelöst in dem die Anwendung in verschiedene Module aufgeteilt wird und diese anhand einer bestimmten Route nachgeladen werden.

In der Praxis könnte das wie im folgenden Beispiel aussehen. Das LazyModule wird nachgeladen, wenn die Route /lazy aufgerufen wird.

const routes: Routes = [
  {
    path: 'lazy',
    loadChildren: () => import('./lazy/lazy.module').then(m => m.LazyModule)
  }
];

Alle Informationen zu Lazy Loading mit dem Angular Router sind unter dem folgenden Link zu finden:

https://angular.io/guide/lazy-loading-ngmodules

Manuelles Lazy Loading in der Praxis

So, nun zum eigentlichen Thema.

Manchmal ist es nötig Module manuell nachzuladen und da reicht es nicht aus, wie in den meisten Anwendungsfällen die Module einfach über den Angular Router nachzuladen.

Ein konkretes Beispiel:

In einem meiner Projekte habe ich beispielsweise eine routenübergreifende Komponente implementiert. Diese ist somit auf jeder Seite verfügbar und hat die Fähigkeit andere Komponenten einzubinden. Da es sich hierbei um relativ viele Komponenten handelt, habe ich diese in verschiedenen Feature-Modulen untergeordnet und lade diese wenn nötig manuell nach.

Damit es jetzt aber nicht zu kompliziert wird, habe ich den folgenden Beispiel-Code auf das Wesentliche heruntergebrochen.

Beispiel Code

Der LoadCallback dient zur Typisierung der Lazy Loading Konfiguration, welche äquivalent zu der des Angular Routers ist.

export declare type LoadCallback = () =>
  Type<any> | NgModuleFactory<any> | Observable<Type<any>> | Promise<NgModuleFactory<any> | Type<any> | any>;

Der LazyLoaderService kümmert sich, wie der Name schon sagt, um das manuelle Lazy Loading. Hier passiert die Magie.

Info: Ab Angular 9 wird nur noch die Ivy-Variante verwendet!

import { Compiler, Injectable, Injector, NgModuleFactory, NgModuleRef, Type } from '@angular/core';
import { LoadCallback } from './load-callback.type.ts';

@Injectable({
  providedIn: 'root',
})
export class LazyLoaderService {

  constructor(private compiler: Compiler, private injector: Injector) {
  }

  loadModule(loadChildren: LoadCallback): Promise<NgModuleRef<any>> {
    return (loadChildren() as Promise<NgModuleFactory<any> | Type<any>>)
      .then((elementModuleOrFactory) => {
        if (elementModuleOrFactory instanceof NgModuleFactory) {
          // if ViewEngine
          return elementModuleOrFactory;
        } else {
          try {
            // if Ivy
            return this.compiler.compileModuleAsync(elementModuleOrFactory);
          } catch (err) {
            throw err;
          }
        }
      })
      .then((moduleFactory) => {
        try {
          return moduleFactory.create(this.injector);
        } catch (err) {
          throw err;
        }
      });
  }
}

In dieser ExampleComponent wird das LazyModule nachgeladen, sobald diese Komponente initialisiert wurde.

import { Component, ComponentFactoryResolver, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { LazyLoaderService } from './lazy-loader.service';
import { LoadCallback } from './load-callback.type.ts';

@Component({
  selector: 'app-example',
  template: '',
})
export class ExampleComponent implements OnInit {
  constructor(private lazyLoaderService: LazyLoaderService) {
  }

  ngOnInit() {
    this.manuallyLoadModule();
  }

  private manuallyLoadModule(): void {
    // Example configuration
    const lazyModuleLoader: LoadCallback = () => import('./lazy/lazy.module').then(m => m.LazyModule);

    return this.lazyLoaderService
      .loadModule(lazyModuleLoader)
      .then((moduleRef) => {
        try {
         const componentFactoryResolver = moduleRef.componentFactoryResolver as ComponentFactoryResolver;

         // Do something with moduleRef or componentFactoryResolver

        } catch (err) {
          throw err;
        }
      });
  }
}

Das war schon alles. Solltest du etwa eine Frage dazu haben oder Unterstützung benötigen, dann hinterlasse einen Kommentar oder kontaktiere mich über das Kontaktformular.

Vorheriger ArtikelNächster Artikel
Daniel Kiesel ist ein freier Softwareentwickler aus der Region Karlsruhe und unterstützt Unternehmen und deren Teams bei der Entwicklung von Softwareprodukten.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.