Manual lazy loading of Angular component and providers with Standalone APIs (without NgModule) 🥢
Tags: lazyloading, angular
Published: 2022-10-26 22:14:20

**With [Angular Standalone APIs](https://angular.io/guide/standalone-components) (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](https://stackblitz.com/edit/angular-standalone-apis-manual-lazy-loading)._

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.

```ts
// 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).

```ts
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`](https://github.com/angular/angular/blob/24950dd2a5b6a69a6afc80b8e2041f99bfd47e06/packages/router/src/utils/config.ts#L27-L29). 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`)](https://github.com/angular/angular/blob/24950dd2a5b6a69a6afc80b8e2041f99bfd47e06/packages/router/src/operators/activate_routes.ts#L196) and then it [uses this `EnvironmentInjector` when creating a component instance](https://github.com/angular/angular/blob/24950dd2a5b6a69a6afc80b8e2041f99bfd47e06/packages/router/src/directives/router_outlet.ts#L301-L302), so the component has access to the `Route`'s `providers`.