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