網站搜尋

typescript - 在 Angular 8 中懶加載模組


我有一個儀表板應用程式,裡面我懶得載入小工具(沒有綁定路線)。

我透過定義一個物件 {name: string, loadChildren: string} 來做這件事。然後在我的 app.module 中,我會使用 provideRoutes(...)。

這將導致 CLI 為每個小部件模塊創建一個區塊。

然後在運行時,我會使用 SystemJsModuleLoader 來加載該字串並獲取一個 NgModuleRef。

使用那個,我可以從模組創建組件,並在 ViewContainerRef 上調用 createComponent。

這是那個函數:

 loadWidget(
    name: string,
    container: ViewContainerRef,
    widget: Widget
  ): Promise<{ instance: WidgetComponent; personlize?: { comp: any; factory: ComponentFactoryResolver } }> {
    if (this.lazyWidgets.hasOwnProperty(name)) {
      return this.loader.load(this.lazyWidgets[name]).then((moduleFactory: NgModuleFactory<any>) => {
        const entryComponent = (<any>moduleFactory.moduleType).entry;
        const moduleRef = moduleFactory.create(this.injector);
        const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);

        const comp = container.createComponent(compFactory);
        (<WidgetComponent>comp.instance).widget = widget;
        const personalize = (<any>moduleFactory.moduleType).personalize;
        if (personalize) {
          return {
            instance: <WidgetComponent>comp.instance,
            personlize: {
              comp: personalize,
              factory: moduleRef.componentFactoryResolver,
              injector: moduleRef.injector
            }
          };
        } else {
          return {
            instance: <WidgetComponent>comp.instance
          };
        }
      });
    } else {
      return new Promise(resolve => {
        resolve();
      });
    }

在 Angular 8 中,loadChildren 變更為 import 函數。

你得到的不是一個 NgModuleRef,而是實際的模組實例。

我以為我可以透過取得那個模組、編譯它以獲得 NgModuleRef,然後保持其餘程式碼不變來修復我的程式碼。

看起來在 AOT 模式下,編譯器並沒有被打包。

所以我現在基本上被困住了,手上有一個我需要的元件實例,但沒有辦法將它加入到視圖容器中。

它需要一個我無法獲得的組件工廠解析器。

我想我的問題是如何在 Angular 8 中將一個元件的實例添加到視圖容器。現在我已經改回使用 loadChildren 的字串版本,但這只會有效直到第 9 版發布為止。

這是使用不適用於 AOT 的編譯器版本

 if (this.lazyWidgets.hasOwnProperty(name)) {
      return this.lazyWidgets[name]().then((mod: any) => {
        const moduleFactory = this.compiler.compileModuleSync(mod);
        const entryComponent = (<any>moduleFactory.moduleType).entry;
        const moduleRef = moduleFactory.create(this.injector);
        const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);

        const comp = container.createComponent(compFactory);
        (<WidgetComponent>comp.instance).widget = widget;
        const personalize = (<any>moduleFactory.moduleType).personalize;
        if (personalize) {
          return {
            instance: <WidgetComponent>comp.instance,
            personlize: {
              comp: personalize,
              factory: moduleRef.componentFactoryResolver,
              injector: moduleRef.injector
            }
          };
        }

這裡是一個例子,展示了我原本是這麼想做的,但後來沒有辦法將它添加到 ViewContainerRef。

該模組實例實現了一個需要有「entry」屬性的介面。

這定義了要載入的實際元件:

  if (this.lazyWidgets.hasOwnProperty(name)) {
      return this.lazyWidgets[name]().then((mod: any) => {

        const comp = mod.entry;
        (<WidgetComponent>comp.instance).widget = widget;
        const personalize = mod.personalize;
        if (personalize) {
          return {
            instance: <WidgetComponent>comp.instance,
            personlize: {
              comp: personalize,
               factory: null //this no longer works:  moduleRef.componentFactoryResolver,
              // injector: moduleRef.injector
            }
          };
        } else {
          return {
            instance: <WidgetComponent>comp.instance
          };
        }
      });
    } 

編輯:

我試著在 Stackblitz 加入一個範例,但編譯器把我的字串變成了函數。至少程式碼比較可讀。這就是我在 Angular 8 中所做的事情。我基本上需要一種方法,用 import() 來取代魔法字串。

https://stackblitz.com/edit/angular-9yaj4l

答案#1

在 Angular 8 中,loadChildren 函數的結果在 JIT 模式下是 NgModule 類型的 Promise,在 AOT 模式下是 NgModuleFactory 的 Promise。

有了這個想法,你可以將你的服務重寫如下:

import { 
    Injectable, Compiler, Injector, Type, 
    ViewContainerRef, ComponentFactoryResolver,
    NgModuleFactory, Inject 
} from '@angular/core';

@Injectable()
export class LazyLoaderService {

  constructor(private injector: Injector,
    private compiler: Compiler,
    @Inject(LAZY_WIDGETS) private lazyWidgets: 
       { [key: string]: () => Promise<NgModuleFactory<any> | Type<any>> }) { }


  async load(name: string, container: ViewContainerRef) {
    const ngModuleOrNgModuleFactory = await this.lazyWidgets[name]();
 
    let moduleFactory;

    if (ngModuleOrNgModuleFactory instanceof NgModuleFactory) {
      // aot mode
      moduleFactory = ngModuleOrNgModuleFactory;
    } else {
      // jit mode
      moduleFactory = await this.compiler.compileModuleAsync(ngModuleOrNgModuleFactory);
    }

    const entryComponent = (<any>moduleFactory.moduleType).entry;
    const moduleRef = moduleFactory.create(this.injector);

    const compFactory = moduleRef.componentFactoryResolver.resolveComponentFactory(entryComponent);

    const comp = container.createComponent(compFactory);
  }   
}

Stackblitz 範例

提示: 當你不確定時,總是要查看原始碼

  • https://github.com/angular/angular/blob/72ecc453639eae017f75653c9004adc406ed2ee6/packages/router/src/router_config_loader.ts#L46-L59

  • https://github.com/angular/angular/blob/32886cf9ace539e14e2b387cd8afb10715c8d3de/aio/src/app/custom-elements/elements-loader.ts#L56-L68

答案#2

看起來在使用 Router 時,lazyWidgets 常數不應該有 name 屬性,而應該有 path 屬性:

export const lazyWidgets: { path: string, loadChildren: () => .....

否則你會收到錯誤:

路由 '' 的配置無效:路由必須指定路徑或匹配器