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: () => .....
否則你會收到錯誤:
路由 '' 的配置無效:路由必須指定路徑或匹配器
