DEV Community

Krzysztof Platis
Krzysztof Platis

Posted on • Updated on

Manual lazy loading of Angular component and providers with Standalone APIs (without NgModule) 🥢

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];
Enter fullscreen mode Exit fullscreen mode

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);
    });
  }
Enter fullscreen mode Exit fullscreen mode

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! 🙌

Buy Me A Coffee

Top comments (1)

Collapse
 
alfmos profile image
Alfonso Moscato • Edited

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