With Angular Standalone APIs (introduced in v14) it’s possible to manually lazy load a component (even with it’s dependency services and providers), similarly to how we would manually lazy load a NgModule
. We just need to create ourselves a child EnvironmentInjector
(simulating what a lazy-loaded NgModule
would do). This is also exactly what the Angular Router
does under the hood since v14, when instantiating a component for a Route
that has it's own providers
array.
TLDR: See the stackblitz example of manually lazy loading a component with a service, with Standalone APIs.
For example, let's say we have a barrel index.ts
with items that we want to lazy load all together - a component and a service. And let's suppose the component depends on the service.
// lazy/index.ts
export * from './lazy.service';
export * from './lazy.component';
// custom naming convention - export an array named `providers`:
export const providers = [LazyService];
Then we can lazy load this barrel and create a child EnvironmentInjector
(containing all barrel's providers
). Later on this injector can be used when instantiating the lazy component (so the component has the access to the providers
mentioned above).
export class AppComponent {
constructor(
protected viewContainerRef: ViewContainerRef,
protected injector: Injector,
protected environmentInjector: EnvironmentInjector
) {}
lazyLoadComponent() {
// 1. lazy load the barrel file
import('./lazy/index').then((lazyBarrel) => {
// 2. create manually an `EnvironmentInjector` with all
// the `providers` exported from the lazy barrel.
// Pass `this.environmentInjector` as a parent.
const childEnvironmentInjector = createEnvironmentInjector(
lazyBarrel.providers,
this.environmentInjector,
'Lazy Environment Injector'
);
// 3. instantiate a lazy component, passing:
// the parent component's element `Injector`
// and the just created child `EnvironmentInjector`
const lazyComponent = createComponent(lazyBarrel.LazyComponent, {
environmentInjector: childEnvironmentInjector,
elementInjector: this.injector,
});
// 4. attach the lazy component inside the parent component
this.viewContainerRef.insert(lazyComponent.hostView);
});
}
Routing-driven lazy loading - Angular 14 source code analysis
The above approach is very similar to how the Angular Router
instantiates components (not only lazy-loaded) under the hood, since version 14. When matching an URL against a Route
that contains an array of providers
, Angular creates a child EnvironmentInjector
. Later on, when the <router-outlet>
instantiates a component for the current Route
, Angular takes the Route
's EnvironmentInjector
(or the closest injector defined in the parent Routes
) and then it uses this EnvironmentInjector
when creating a component instance, so the component has access to the Route
's providers
.
If you really feel like buying me a coffee
... then feel free to do it. Many thanks! 🙌
Top comments (1)
Thank you for this article. There are very few articles on CreateEnvironmentInjector, and documentation is not very clear.
I was stuck with provideEffects for a component lazy loaded without having a route where to create EnvironmentProviders. This has pointed me in the right direction