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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.