DEV Community

negue
negue

Posted on

Lazy Loaded Components - #2

Extending the features of the component-level lazy loader thanks to 🎉 Ivy 🎉

Continue from Part 1:

1. Inject services into lazy-loaded components:

Since the loader uses the same injector-Instance we can inject the same services as the parent Component.

Side-note: The usual lifecycle callbacks are still working on lazy-loaded components.

2. @Input()

To set the inputs on the loaded component, the loader uses componentInputs: any as @Input()

  private setInputs() {
    if (this.componentInstance && this.componentInputs) {
      const inputs = Object.keys(this.componentInputs);

      for (const inputKey of inputs) {
        this.componentInstance[inputKey] = this.componentInputs[inputKey];
      }
    }
  }

setInputs will be called once after the component is created and on each ngOnChanges that is called for componentInputs.

Now you can also set inputs to the loaded component 🎉

3. @Output()

In order to use the outputs of your loaded component, you can just set your callbacks with:

[componentOutputs]="{
 outputName: onYourCallbackMethod
}"

Since this object is just a dictionary of key: Function, its rather "easy" to subscribe to the loaded component outputs. 🎉

  private unsubForOutputs$ = new Subject();
  private setOutputs () {
    this.unsubOutputs();

    if (this.componentInstance && this.componentOutputs) {
      const outputs = Object.keys(this.componentOutputs);

      for (const outputKey of outputs) {
        if (this.componentInstance[outputKey]) {
          const emitter = this.componentInstance[outputKey] as EventEmitter<any>;
            emitter.pipe(
              takeUntil(this.unsubForOutputs$),
            ).subscribe(this.componentOutputs[outputKey]);
        }
      }
    }
  }

  private unsubOutputs () {
    this.unsubForOutputs$.next();
  }

4. Prevent loading the same components multiple times

The prior example had this:

const imported = await DynamicLoaderComponent.LazyComponents[this.component]();

That way the requested component would be loaded (HTTP-call) every time.

To prevent this we can just add a simple cache - object which holds the resolved-promises (in the same way I refactored the registration (again 😅)):

export class DynamicLoaderRegistry {
  // Registry
  public static LazyComponents: { [key: string]: () => Promise<any> } = {};
  // Loaded-Cache
  public static AlreadyLoaded: { [key: string]: Promise<any> } = {};
}


// cache the promises
    const importComponent = DynamicLoaderRegistry.AlreadyLoaded[this.component]
      || (DynamicLoaderRegistry.AlreadyLoaded[this.component] = DynamicLoaderRegistry.LazyComponents[this.component]());

    const imported = await importComponent;

Now if the same component is requested, its only loaded once 🎉

Finalizing the component-loader 🎉 (.. for now 😅)

See: Current Version

to be continued / tried / tested:

  • example repo / project
  • show that the component is loading
  • lazy-loaded module (and one of its components)

Any thoughts / suggestions / ideas ?

Top comments (0)