Skip to content

Instantly share code, notes, and snippets.

@kirill-chirkov-at-clouty
Last active November 19, 2019 13:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save kirill-chirkov-at-clouty/db37a02b212af6d64c021be71024f135 to your computer and use it in GitHub Desktop.
Save kirill-chirkov-at-clouty/db37a02b212af6d64c021be71024f135 to your computer and use it in GitHub Desktop.
Lazy angular module loading
import { Injectable, NgModuleFactory } from '@angular/core';
import { first } from 'rxjs/operators';
import { OperatorSignupComponent } from '../../../components/operator-signup/operator-signup.component';
import { OperatorSignupModule } from '../../../components/operator-signup/operator-signup.module';
import { DialogService } from '../../../ui/dialog/dialog.service';
import { ModuleKey } from '../../module-loader/models/module-key.enum';
import { ModuleLoaderService } from '../../module-loader/module-loader.service';
import { OperatorSignupDataModel } from './models/operator-signup-data.model';
// loadChildren is essential for compiler to work, fixed in Ivy
// https://github.com/angular/angular/issues/32194
export const OPERATOR_MODULE_LOADER = {
loadChildren: () => import('../../../components/operator-signup/operator-signup.module').then(m => m.OperatorSignupModule)
};
@Injectable({
providedIn: 'root',
})
export class OperatorModalService {
constructor(
private dialog: DialogService,
private moduleLoader: ModuleLoaderService,
) {
}
public open(payload: OperatorSignupDataModel): void {
this.moduleLoader.moduleChanges<OperatorSignupModule>(ModuleKey.PROMO_SIGNUP, OPERATOR_MODULE_LOADER.loadChildren)
.pipe(
first(),
)
.subscribe((ngModuleFactory: NgModuleFactory<OperatorSignupModule>) => {
this.dialog.open(OperatorSignupComponent, {ngModuleFactory, data: payload});
});
}
}
import { Compiler, Injectable, Injector, NgModuleFactory, Type } from '@angular/core';
import { from, Observable, of, throwError } from 'rxjs';
import { filter, switchMap, catchError } from 'rxjs/operators';
import { StoreKey } from '../../config/store-key.enum';
import { takeUntilDestroyed } from '../../helpers/observables/take-until-destroyed';
import { StoreService } from '../store/store.service';
import { ModuleKey } from './models/module-key.enum';
@Injectable({
providedIn: 'root',
})
export class ModuleLoaderService {
constructor(
private store: StoreService,
private injector: Injector,
) {
}
public moduleChanges<T>(moduleKey: ModuleKey, importLine: () => Promise<NgModuleFactory<T> | Type<T>>): Observable<NgModuleFactory<T>> {
const storeKey = this.keyOf(moduleKey);
if (!this.store.has(storeKey)) {
this.store.set(storeKey, null);
this.loadModule(moduleKey, importLine);
}
return this.store.itemChanges<NgModuleFactory<T>>(storeKey)
.pipe(
filter((value) => Boolean(value)),
);
}
private loadModule<T>(moduleKey: ModuleKey, importLine: () => Promise<NgModuleFactory<T> | Type<T>>): void {
from(importLine())
.pipe(
switchMap((moduleFactoryOrType: NgModuleFactory<T> | Type<T>) => this.compile(moduleFactoryOrType)),
takeUntilDestroyed(this),
)
.subscribe((moduleFactory: NgModuleFactory<T>) => {
this.store.set(this.keyOf(moduleKey), moduleFactory);
});
}
/** Convert type to factory */
private compile<T>(type: NgModuleFactory<T> | Type<T>): Observable<NgModuleFactory<T>> {
// AOT mode
if (type instanceof NgModuleFactory) {
return of(type as NgModuleFactory<T>);
}
try {
// Seems like we're in JIT mode in here
const compiler = this.injector.get(Compiler);
if (compiler) {
try {
return from(compiler.compileModuleAsync(type as Type<T>));
} catch (e) {
console.error(`Module loader failed to compile type ${type}`);
console.error(e);
return of(null);
}
} else {
return of(null);
}
} catch (e) {
return of(null);
}
}
private keyOf(moduleKey: ModuleKey): [StoreKey.LAZY_MODULE, string] {
return [StoreKey.LAZY_MODULE, `${moduleKey}`];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment