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 ?

Discussion (0)