Skip to content

Instantly share code, notes, and snippets.

@negue
Last active April 26, 2020 10:00
Show Gist options
  • Save negue/5f4435c7e1d2c11449691d342b39cdd5 to your computer and use it in GitHub Desktop.
Save negue/5f4435c7e1d2c11449691d342b39cdd5 to your computer and use it in GitHub Desktop.
Lazy Component Loader
import {
Component,
ChangeDetectionStrategy,
ViewChild,
ViewContainerRef,
ComponentFactoryResolver,
Injector, Input, OnChanges,
SimpleChanges, EventEmitter, OnDestroy, Output, ChangeDetectorRef, OnInit
} from '@angular/core';
import {takeUntil} from 'rxjs/operators';
import {Subject} from 'rxjs';
import {DynamicLoaderRegistry} from './dynamic-loader.registry';
interface InputMap {
[key: string]: any;
}
interface OutputMap {
[key: string]: (value: any) => void;
}
@Component({
selector: 'helpers-dynamic-loader',
template: `
<ng-container #targetContainer>
</ng-container>
<ng-content *ngIf="!component"></ng-content>
<ng-content *ngIf="componentLoading | async"
select="[isLoading]"></ng-content>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class DynamicLoaderComponent implements OnInit, OnChanges, OnDestroy {
@Input()
public component: string;
@Input()
public componentInputs: InputMap;
@Input()
public componentOutputs: OutputMap;
@ViewChild('targetContainer', {read: ViewContainerRef, static: true})
public targetContainer: ViewContainerRef;
@Output()
public componentCreated = new EventEmitter();
@Output()
public componentLoading = new EventEmitter();
private componentInstance = null;
private unsubForOutputs$ = new Subject();
constructor (private resolver: ComponentFactoryResolver,
private injector: Injector,
private cd: ChangeDetectorRef) {
}
async setComponent () {
if (!this.component) {
return;
}
this.componentLoading.emit(true);
this.cd.detectChanges();
// cached promise
const importComponent = DynamicLoaderRegistry.AlreadyLoaded[this.component]
|| (DynamicLoaderRegistry.AlreadyLoaded[this.component] = DynamicLoaderRegistry.LazyComponents[this.component]());
const imported = await importComponent;
const keys = Object.keys(imported);
// get the first object of the imported js-module
const theComp = imported[keys[0]];
const componentFactory = this.resolver.resolveComponentFactory(theComp);
// only have one dynamic component render
this.targetContainer.clear();
const componentRef = this.targetContainer.createComponent(componentFactory, 0, this.injector);
componentRef.changeDetectorRef.markForCheck();
this.componentLoading.emit(false);
this.componentCreated.emit(componentRef.instance);
this.componentInstance = componentRef.instance;
this.setInputs();
this.setOutputs();
}
ngOnChanges (changes: SimpleChanges): void {
if (changes['component']) {
this.setComponent();
}
if (changes['componentInputs']) {
this.setInputs();
}
if (changes['componentOutputs']) {
this.setOutputs();
}
}
ngOnDestroy () {
this.unsubOutputs();
}
private setInputs () {
// console.info('setInputs', this.componentInstance, this.componentInputs);
if (this.componentInstance && this.componentInputs) {
const inputs = Object.keys(this.componentInputs);
for (const inputKey of inputs) {
// console.info('set ', inputKey)
this.componentInstance[inputKey] = this.componentInputs[inputKey];
}
}
}
private unsubOutputs () {
this.unsubForOutputs$.next();
}
private setOutputs () {
this.unsubOutputs();
if (this.componentInstance && this.componentOutputs) {
const outputs = Object.keys(this.componentOutputs);
for (const outputKey of outputs) {
// console.info('subscribe to', outputKey);
if (this.componentInstance[outputKey]) {
const emitter = this.componentInstance[outputKey] as EventEmitter<any>;
emitter.pipe(
takeUntil(this.unsubForOutputs$),
).subscribe(this.componentOutputs[outputKey]);
}
}
}
}
ngOnInit (): void {
this.setComponent();
}
}
export class DynamicLoaderRegistry {
// Registry
public static LazyComponents: { [key: string]: () => Promise<any> } = {};
// Loaded-Cache
public static AlreadyLoaded: { [key: string]: Promise<any> } = {};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment