1
\$\begingroup\$

I want create a generic component for lazy loading other component

Demo online (click on load button) https://stackblitz.com/edit/angular-v7xfwb

this is the code

import {
  Component,
  Injector,
  Input,
  ViewContainerRef,
  ViewChild,
  Type,
  createNgModule,
  ComponentRef,
  EventEmitter,
  OnInit,
  NgModule,
} from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'ng-lazy-component',
  template: `
<ng-content *ngIf="isLoading" select="[loading]">
</ng-content>
<ng-content *ngIf="error" select="[error]">
</ng-content>
<ng-container #vcRef></ng-container>
`,
})
export class NgLazyComponentComponent implements OnInit {
  @ViewChild('vcRef', { read: ViewContainerRef }) vcRef: ViewContainerRef;

  @Input() importModule: () => Promise<Type<any>>;

  _componentInput: Record<string, any>;
  @Input()
  set componentInput(value: Record<string, any>) {
    this._componentInput = value;
    this.setInput();
  }

  _componentOutput: Record<string, EventEmitter<any>>;
  @Input()
  set componentOutput(value: Record<string, EventEmitter<any>>) {
    this._componentOutput = value;
    this.setOutput();
  }

  public isLoading: boolean;
  public error = null;

  private componentRef: ComponentRef<any>;

  constructor(private injector: Injector) {}

  ngOnInit(): void {
    this.loadModule();
  }

  async loadModule() {
    try {
      this.isLoading = true;
      if (this.componentRef) {
        this.vcRef.clear();
      }
      const module = await this.importModule();
      const lazyModuleRef = createNgModule(module, this.injector);
      if (typeof lazyModuleRef.instance.getComponent !== 'function') {
        throw new Error(
          `${module.name} need to implement getComponent()`
        );
      }
      const component = lazyModuleRef.instance.getComponent();
      this.componentRef = this.vcRef.createComponent(component, {
        ngModuleRef: lazyModuleRef,
      });
      this.setInput();
      this.setOutput();
      this.componentRef.changeDetectorRef.detectChanges();
      this.isLoading = false;
    } catch (err) {
      this.error = err;
      throw err;
    } finally {
      this.isLoading = false;
    }
  }

  setInput() {
    if (this.componentRef && this._componentInput) {
      for (const key in this._componentInput) {
        this.componentRef.setInput(key, this._componentInput[key]);
      }
    }
  }

  setOutput() {
    if (this.componentRef && this._componentOutput) {
      for (const key in this._componentOutput) {
        if (!(this.componentRef.instance[key] instanceof EventEmitter)) {
          throw new Error(`@Output() ${key}:EventEmitter - not found!`);
        }
        (this.componentRef.instance[key] as EventEmitter<any>).subscribe(
          (value) => this._componentOutput[key].emit(value)
        );
      }
    }
  }
}

@NgModule({
  declarations: [NgLazyComponentComponent],
  imports: [CommonModule],
  exports: [NgLazyComponentComponent],
})
export class NgLazyComponentModule {}

usage:

import 'zone.js/dist/zone';
import { Component, EventEmitter, Type } from '@angular/core';
import { CommonModule } from '@angular/common';
import { bootstrapApplication } from '@angular/platform-browser';
import { Subscription } from 'rxjs';
import { NgLazyComponentModule } from './ng-lazy-component.component';

@Component({
  selector: 'my-app',
  standalone: true,
  imports: [CommonModule, NgLazyComponentModule],
  template: `
  <button (click)="loaded = false" [disabled]="!loaded">Unload</button> <button (click)="loaded = true" [disabled]="loaded">Load</button>
  <hr>
  <ng-lazy-component [importModule]="importModule" [componentInput]="componentInput" [componentOutput]="componentOutput" *ngIf="loaded"></ng-lazy-component>
  `,
})
export class App {
  public loaded = false;

  public componentInput: Record<string, any> = { testInput: 0 };
  public componentOutput: Record<string, EventEmitter<any>> = {
    testOutput: new EventEmitter<number>(),
  };

  private subComponentOutput: Subscription;

  importModule(): Promise<Type<any>> {
    return import('./test-lazy.module').then((m) => m.TestLazyModule);
  }

  ngOnInit(): void {
    this.subComponentOutput = this.componentOutput.testOutput.subscribe(
      (value) => {
        this.componentInput = {
          ...this.componentInput,
          testInput: value + 1,
        };
      }
    );
  }

  ngOnDestroy(): void {
    if (this.subComponentOutput) {
      this.subComponentOutput.unsubscribe();
    }
  }
}

I want some opinions on possible problem of my implementation

\$\endgroup\$

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.