typescript - Carregar módulo de forma preguiçosa no angular 8
Eu tenho um aplicativo de painel onde eu estava carregando widgets preguiçosamente (não vinculados a uma rota).
Eu estava fazendo isso definindo um objeto {name: string, loadChildren: string}. Então, no meu app.module, eu faria provideRoutes(...).
Isso faria com que o CLI criasse um chunk para cada módulo de widget.
Então, em tempo de execução, eu usaria o SystemJsModuleLoader para carregar essa string e obter um NgModuleRef.
Usando isso, eu poderia criar o componente a partir do módulo e chamar createComponent no ViewContainerRef.
Aqui está essa função:
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();
});
}
No Angular 8, o loadChildren muda para a função import.
Em vez de um NgModuleRef, você obtém a instância real do módulo.
Eu pensei que poderia consertar meu código pegando aquele módulo, compilando-o para obter o NgModuleRef e depois mantendo o resto do código igual.
Parece que no modo AOT, porém, o compilador não é incluído no pacote.
Então, basicamente estou preso agora com uma instância do componente que eu preciso, mas sem maneira de adicioná-lo ao contêiner View.
Isso requer um resolvedor de fábrica de componentes que eu não consigo obter.
Eu acho que minha pergunta é como pegar uma instância de um componente e adicioná-la ao container de visualização no Angular 8. Por enquanto, voltei a usar a versão em string do loadChildren, mas isso só funcionará até a versão 9 ser lançada.
Aqui está a versão com o compilador que não funciona em 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
}
};
}
E aqui está um exemplo de como eu estava pensando em fazer isso, mas então não havia uma maneira de adicioná-lo ao ViewContainerRef.
A instância do módulo implementa uma interface que requer uma propriedade 'entry'.
Isto define o componente real a ser carregado:
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
};
}
});
}
EDITAR:
Tentei adicionar um exemplo no stackblitz, mas o compilador está transformando minha string em funções. Pelo menos o código está mais legível. Isso é o que eu estava fazendo no Angular 8. Eu basicamente preciso de uma maneira de fazer isso com import() em vez de string mágica.
https://stackblitz.com/edit/angular-9yaj4l
Resposta #1
No Angular 8, o resultado da função loadChildren é ou um Promise do tipo NgModule no modo JIT ou um Promise de NgModuleFactory no modo AOT.
Com isso em mente, você pode reescrever seu serviço da seguinte forma:
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);
}
}
Exemplo Stackblitz
Dica: Sempre olhe o código-fonte quando estiver em dúvida
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
Resposta #2
Parece que ao usar o Router, a constante lazyWidgets não deveria ter a propriedade name, mas sim a propriedade path:
export const lazyWidgets: { path: string, loadChildren: () => .....
Caso contrário, você receberá erro:
Configuração inválida da rota '': as rotas devem ter um caminho ou um combinador especificado
