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
Lazy loading is an approach to limit the modules that are loaded to the ones that the user currently needs. This can improve your application's performance and reduce the initial bundle size. By default, Angular uses eager loading to load modules.
To lazy load Angular modules, use loadChildren (instead of component ) in your AppRoutingModule routes configuration as follows. content_copy const routes: Routes = [ { path: 'items', loadChildren: () => import('./items/items. module'). then(m => m.
Lazy loading is the process of loading components, modules, or other assets of a website as they're required. Since Angular creates a SPA (Single Page Application), all of its components are loaded at once. This means that a lot of unnecessary libraries or modules might be loaded as well.
Lazy loading is a technique in Angular that allows you to load JavaScript components asynchronously when a specific route is activated. It improves the speed of the application load time by splitting the application into several bundles. When the user navigates through the app, the bundles are loaded as required.
As of Angular 8, you can generate a feature module with routing enabled, a default component and add as a lazy loaded route to the router. The above command must have the following flags: --module flag ( Required) – The module where to register the route for the app.
To use lazy loading, first, you need to create some feature modules. Feature modules are NgModules created for the purpose of code organization. Feature modules allow you to separate code for a feature/functionality from the rest of your app. Components, Pipes, Directives, etc.
In Angular version 8, the string syntax for the loadChildren route specification was deprecated in favor of the import () syntax. However, you can opt into using string-based lazy loading (loadChildren: './path/to/module#Module') by including the lazy-loaded routes in your tsconfig file, which includes the lazy-loaded files in the compilation.
In the lazy-loaded module's routing module, add a route for the component. Also be sure to remove the ItemsModule from the AppModule . For step-by-step instructions on lazy loading modules, continue with the following sections of this page. There are two main steps to setting up a lazy-loaded feature module:
In Angular 8 the result of loadChildren
function is either Promise
of NgModule
type in JIT mode or Promise
of NgModuleFactory
in AOT mode.
With this in mind you can rewrite your service as follows:
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 Example
Tip: Always look at the source code when you're in doubt
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
It seems that when using Router, lazyWidgets
const should have not name
but path
property:
export const lazyWidgets: { path: string, loadChildren: () => .....
Otherwise you'll get error:
Invalid configuration of route '': routes must have either a path or a matcher specified
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With