I have a dashboard app where I was lazy loading widgets (not tied to a route).
I was doing this by defining an object {name: string, loadChildren: string}
. Then in my app.module
I would do provideRoutes(...)
.
This would cause the cli to create a chunk for each widget module.
Then at runtime I would use the SystemJsModuleLoader
to load that string and get an NgModuleRef
.
Using that I could create the component from the module and call createComponent
on the ViewContainerRef
.
Here is that function:
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();
});
}
In angular 8 the loadChildren
changes to the import function.
Instead of an NgModuleRef
you get the actual module instance.
I thought I could fix my code by taking that module, compiling it to get the NgModuleRef
then keeping the rest of the code the same.
It seems that in AOT mode though the compiler does not get bundled.
So I am basically stuck now with an instance of the component I need but no way to add it to the View container.
It requires a component factory resolver which I can't get.
I guess my question is how to take an instance of a component and add it to view container in angular 8. For now I have reverted to using the string version of loadChildren but that will only work until version 9 comes out.
Here is the version with the compiler that does not work in 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
}
};
}
And here is an example of how I was thinking to do it but then having no way to add it to the ViewContainerRef
.
The module instance implements an interface that requires an 'entry' property.
This defines the actual component to load:
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
};
}
});
}
EDIT:
I tried to add an example in stackblitz but the compiler is turning my string to functions. At least the code is more readable. this is what I was doing in angular 8. I basically need a way to do this with import()
instead of magic string.
https://stackblitz.com/edit/angular-9yaj4l