UNPKG

ngx-mfe

Version:

A library for working with MFE in Angular in a plugin-based approach and with Angular routing.

867 lines (851 loc) 33.4 kB
import { __decorate } from 'tslib'; import * as i0 from '@angular/core'; import { InjectionToken, EventEmitter, Injectable, createNgModuleRef, ChangeDetectorRef, TemplateRef, Input, Inject, Directive, APP_INITIALIZER, NgModule } from '@angular/core'; import { loadRemoteModule, loadRemoteEntry } from '@angular-architects/module-federation'; import { ReplaySubject, take, map, firstValueFrom, Subject, takeUntil, AsyncSubject, lastValueFrom, from, tap } from 'rxjs'; /** * Strategy for changing the component's @Input() variable. */ var EChangesStrategy; (function (EChangesStrategy) { /** * Called on every change. */ EChangesStrategy[EChangesStrategy["Each"] = 0] = "Each"; /** * Called only on the first change. */ EChangesStrategy[EChangesStrategy["First"] = 1] = "First"; /** * Called on every change except the first. */ EChangesStrategy[EChangesStrategy["NonFirst"] = 2] = "NonFirst"; })(EChangesStrategy || (EChangesStrategy = {})); const defaultOptions = { strategy: EChangesStrategy.Each, compare: false, }; /** * Decorator of lifecycle hook ngOnChanges, that call specified method when changes prop (@Input) value. * ------- * * Method decorator. * * @param prop Variable name of Input, that will be call method when changes. * @param methodName The name of the method that will be called when the variable changes. * @param options Options. */ function TrackChanges(prop, methodName, options) { return function (target, _propertyKey, descriptor) { const _options = { ...defaultOptions, ...options }; const originalMethod = descriptor.value; descriptor.value = function (changes) { if (changes && changes[prop] && changes[prop].currentValue !== undefined) { const isFirstChange = changes[prop].firstChange; const shouldCompareValues = _options.compare; const isValuesDifference = changes[prop].previousValue !== changes[prop].currentValue; if (_options.strategy === EChangesStrategy.Each || (_options.strategy === EChangesStrategy.First && isFirstChange) || (_options.strategy === EChangesStrategy.NonFirst && !isFirstChange)) { if (!shouldCompareValues) { target[methodName].call(this, changes[prop].currentValue); } else if (isValuesDifference) { target[methodName].call(this, changes[prop].currentValue); } } } originalMethod.call(this, changes); }; return descriptor; }; } const delay = (time) => new Promise((resolve) => setTimeout(resolve, time)); /** * Registry of micro-frontends apps. */ class MfeRegistry { /** * Get instance of the MfeRegistry */ static get instance() { if (!MfeRegistry._instance) { MfeRegistry._instance = new MfeRegistry(); } return MfeRegistry._instance; } constructor() { this._mfeConfig$ = new ReplaySubject(1); } /** * Set config. * @param config Micro-frontends config */ setMfeConfig(config) { this._mfeConfig$.next(config); } /** * Get the remote entry URL the micro-frontend app * @param mfeApp Micro-frontend app name */ getMfeRemoteEntry(mfeApp) { return this._mfeConfig$.pipe(take(1), map((config) => { const remoteEntry = config[mfeApp]; if (!remoteEntry) { throw new Error(`'${mfeApp}' micro-frontend is not registered in the MfeRegistery using MfeModule.forRoot({ mfeConfig })`); } return remoteEntry; })); } } const loadMfeDefaultOptions = { type: 'module' }; /** * Loads remote bundle. * * @param remoteApp The name of the micro-frontend app decalred in ModuleFederationPlugin. * @param exposedModule The key of the exposed module decalred in ModuleFederationPlugin. * @param options (Optional) object of options. */ async function loadMfe(remoteApp, exposedModule, options = loadMfeDefaultOptions) { const _options = { ...loadMfeDefaultOptions, ...options }; const remoteEntry = await firstValueFrom(MfeRegistry.instance.getMfeRemoteEntry(remoteApp)); const loadRemoteModuleOptions = _options.type === 'module' ? { type: _options.type, remoteEntry, exposedModule } : { type: _options.type, remoteEntry, exposedModule, remoteName: remoteApp }; const bundle = await loadRemoteModule(loadRemoteModuleOptions); const moduleName = _options.moduleName ?? exposedModule; const module = bundle[moduleName]; if (!module) { throw new Error(`Module with name "${moduleName}" does not exist in the exposed file. Key of exposed file must match with class name in this file (Key of exposed file it is key of 'exposes' object in webpack config inside ModuleFederationPlugin).`); } return module; } /** * InjectionToken of options. */ const NGX_MFE_OPTIONS = new InjectionToken('ngx-mfe/options'); /** * Type guard check that NgxMfeConfig is async list of available micro-frontends. */ const isNgxMfeConfigAsync = (config) => { return Object.prototype.hasOwnProperty.call(config, 'useLoader'); }; /** * Type Guard for RemoteComponent, checks if RemoteComponent is Standalone * @param remoteComponent Mfe Remote Component * @returns */ function isStandaloneRemoteComponent(remoteComponent) { return !Object.prototype.hasOwnProperty.call(remoteComponent, 'module'); } /** * Type Guard for RemoteComponent, checks if RemoteComponent is Modular * @param remoteComponent Mfe Remote Component * @returns */ function isRemoteComponentWithModule(remoteComponent) { return Object.prototype.hasOwnProperty.call(remoteComponent, 'module'); } /** * The service that binds the dynamic component. */ class DynamicComponentBinding { constructor() { this._destroy$ = new Subject(); } ngOnDestroy() { this._destroy$.next(); this._destroy$.complete(); } /** * Bind provided MfeOutletInputs to dynamic component. * @param componentRef Reference of component. * @param inputs Provided MfeOutletInputs. */ bindInputs(componentRef, inputs) { for (const key in inputs) { if (Object.prototype.hasOwnProperty.call(inputs, key)) { componentRef.instance[key] = inputs[key]; } } } /** * Bind provided MfeOutletOutputs to dynamic component. * @param componentRef Reference of component. * @param outputs Provided MfeOutletOutputs. */ bindOutputs(componentRef, outputs) { this._validateOutputs(componentRef, outputs); for (const key in outputs) { if (Object.prototype.hasOwnProperty.call(outputs, key)) { componentRef.instance[key] .pipe(takeUntil(this._destroy$)) .subscribe((event) => { const handler = outputs[key]; if (handler) { // in case the output has not been provided at all handler(event); } }); } } } /** * Unbind all outputs. */ unbindOutputs() { this._destroy$.next(); } /** * Validate MfeOutletOutputs of dynamic component. * @param componentRef Reference of component. * @param outputs Provided MfeOutletOutputs. */ _validateOutputs(componentRef, outputs) { Object.keys(outputs).forEach((key) => { const isComponentHaveOutput = Object.prototype.hasOwnProperty.call(componentRef.instance, key); if (!isComponentHaveOutput) { throw new Error(`Dynamically bound Output "${key}" is not declared in target component ${componentRef.componentType.constructor.name}.`); } if (!(componentRef.instance[key] instanceof EventEmitter)) { throw new Error(`Dynamically bound Output "${key}" must be an instance of EventEmitter.`); } if (!(outputs[key] instanceof Function)) { throw new Error(`Dynamically bound Output "${key}" must be a function.`); } }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DynamicComponentBinding, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DynamicComponentBinding }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: DynamicComponentBinding, decorators: [{ type: Injectable }] }); /** * Cache of the loaded micro-frontend apps. * * Main reasons to create cache: * 1) Avoid race condition, when same micro-frontend are requested twice or more times at the same time. * 2) Cache already loaded MFE component and dont make same request twice. */ class RemoteComponentsCache { constructor() { this._map = new Map(); } /** * Register a new micro-frontend cache. * @param remoteComponent Mfe Remote Component */ register(remoteComponent) { if (this.isRegistered(remoteComponent)) return; const key = this.generateKey(remoteComponent); this._map.set(key, new AsyncSubject()); } /** * Unregister a micro-frontend cache. * @param remoteComponent Mfe Remote Component */ unregister(remoteComponent) { if (!this.isRegistered(remoteComponent)) return; const key = this.generateKey(remoteComponent); this._map.delete(key); } /** * Checks that specified micro-frontend app already registered. * @param remoteComponent Mfe Remote Component */ isRegistered(remoteComponent) { const key = this.generateKey(remoteComponent); return this._map.has(key); } setValue(remoteComponent, value) { if (!this.isRegistered(remoteComponent)) { throw new Error(`Error while trying to set value into MFE cache, this key - "${JSON.stringify(remoteComponent)}" does not exist in cache`); } if (isStandaloneRemoteComponent(remoteComponent)) { const cache = this.getCache(remoteComponent); cache.next(value); cache.complete(); return; } const cache = this.getCache(remoteComponent); cache.next(value); cache.complete(); } /** * Sets the error that occurs in the loading and compiling micro-frontend. * @param remoteComponent Mfe Remote Component * @param error Error */ setError(remoteComponent, error) { if (!this.isRegistered(remoteComponent)) { throw new Error(`Error while trying to set error into MFE cache, this key - "${JSON.stringify(remoteComponent)}" does not exist in cache`); } const cache = this.getCache(remoteComponent); cache.error(error); cache.complete(); } getValue(remoteComponent) { if (isStandaloneRemoteComponent(remoteComponent)) { const cache = this.getCache(remoteComponent); return lastValueFrom(cache); } const cache = this.getCache(remoteComponent); return lastValueFrom(cache); } getCache(remoteComponent) { const key = this.generateKey(remoteComponent); const value = this._map.get(key); if (!value) throw new Error(`Error MFE "${JSON.stringify(remoteComponent)}" does not exist in cache`); if (isStandaloneRemoteComponent(remoteComponent)) { return value; } return value; } /** * Generates a cache key based on RemoteComponent * @param remoteComponent Mfe Remote Component */ generateKey(remoteComponent) { if (isRemoteComponentWithModule(remoteComponent)) { return `${remoteComponent.app}/${remoteComponent.component}/${remoteComponent.module}`; } return `${remoteComponent.app}/${remoteComponent.component}`; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentsCache, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentsCache, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentsCache, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }] }); /** * A low-level service for loading a remote micro-frontend component. */ class RemoteComponentLoader { constructor(_ngZone, _injector, _cache) { this._ngZone = _ngZone; this._injector = _injector; this._cache = _cache; } /** * Loads a remote component with module where was declared this component. * @param remoteComponent Remote component. * @param injector (Optional) Injector, use root injector by default. * @param options (Optional) object of options. */ async loadComponentWithModule(remoteComponent, injector = this._injector, options) { try { if (this._cache.isRegistered(remoteComponent)) { return this._cache.getValue(remoteComponent); } this._cache.register(remoteComponent); const { component, module } = await this._ngZone.runOutsideAngular(async () => { const component = await loadMfe(remoteComponent.app, remoteComponent.component, options); const module = await loadMfe(remoteComponent.app, remoteComponent.module, options); return { component, module }; }); const ngModuleRef = createNgModuleRef(module, injector); const componentWithNgModuleRef = { component, ngModuleRef }; this._cache.setValue(remoteComponent, componentWithNgModuleRef); return componentWithNgModuleRef; } catch (error) { this._cache.setError(remoteComponent, error); throw error; } } /** * Loads a standalone remote component. * @param remoteComponent Remote component * @param options (Optional) object of options. */ async loadStandaloneComponent(remoteComponent, options) { try { if (this._cache.isRegistered(remoteComponent)) { return this._cache.getValue(remoteComponent); } this._cache.register(remoteComponent); const componentType = await this._ngZone.runOutsideAngular(() => loadMfe(remoteComponent.app, remoteComponent.component, options)); this._cache.setValue(remoteComponent, componentType); return componentType; } catch (error) { this._cache.setError(remoteComponent, error); throw error; } } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentLoader, deps: [{ token: i0.NgZone }, { token: i0.Injector }, { token: RemoteComponentsCache }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentLoader, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: RemoteComponentLoader, decorators: [{ type: Injectable, args: [{ providedIn: 'root', }] }], ctorParameters: () => [{ type: i0.NgZone }, { type: i0.Injector }, { type: RemoteComponentsCache }] }); /** * Micro-frontend directive for plugin-based approach. * ------------- * * This directive give you to load micro-frontend inside in HTML template. * * @example Loads remote component and show as embed view or as a plugin. * ```html * <!-- Loads EntryComponent that declared in EntryModule from dashboard micro-frontend app. --> * <ng-container *mfeOutlet=" * 'dashboard'; * module: 'EntryModule'; * component: 'EntryComponent'; * "> * </ng-container> * * <!-- Or you can use ng-template, next example works same as previous example --> * <ng-template * mfeOutlet="dashboard" * mfeOutletModule="EntryModule" * mfeOutletComponent="EntryComponent" * > * </ng-template> * ``` * * @example Loads standalone remote component. Standalone component - it is a component that does not depend on anything and does not need dependencies from other modules. * ```html * <!-- You can load a standalone component without declaring a module in the mfeOutletModule prop. --> * <ng-template * mfeOutlet="dashboard" * mfeOutletComponent="EntryComponent" * > * </ng-template> * ``` * * @example You can sets Inputs and sets handlers for Output events of the Remote component. * ```html * <ng-container *mfeOutlet=" * 'dashboard'; * module: 'EntryModule'; * component: 'EntryComponent'; * inputs: { text: text$ | async }; * outputs: { click: onClick } * "> * </ng-container> *``` * * @example Loads remote component and sets custom loader, same approach for fallback view. * ```html * <ng-template * mfeOutlet="dashboard" * mfeOutletModule="EntryModule" * mfeOutletComponent="EntryComponent" * [mfeOutletLoader]="loaderMfe" * [mfeOutletLoaderDelay]="2000" * > * </ng-template> * * <!-- You can specify simple HTML content or declare another MFE component, like in the example below. --> * <ng-template #loaderMfe> * <!-- For loader Mfe you should set mfeOutletLoader to undefined, and mfeOutletLoaderDelay to 0. For better UX. --> * <ng-template * mfeOutlet="loaders" * mfeOutletModule="SpinnerModule" * mfeOutletComponent="SpinnerComponent" * [mfeOutletLoader]="undefined" * [mfeOutletLoaderDelay]="0" * > * </ng-template> * </ng-template> * * <!-- Simple HTML content. --> * <ng-template #loader> * <div>loading...</div> * </ng-template> * ``` */ class MfeOutletDirective { /** * MFE RemoteComponent or TemplateRef. * Displayed when loading the micro-frontend. * * **Overrides the loader specified in the global library settings.** * @default options.loader */ set loader(value) { this._loader = value; } /** * MFE RemoteComponent or TemplateRef. * Displayed when loaded or compiled a micro-frontend with an error. * * **Overrides fallback the specified in the global library settings.** * @default options.fallback */ set fallback(value) { this._fallback = value; } /** * Remote component object. */ get _remoteComponent() { if (this.mfeModule) { return { app: this.mfeApp, component: this.mfeComponent, module: this.mfeModule, }; } return { app: this.mfeApp, component: this.mfeComponent, }; } constructor(_vcr, // INSTEAD OF USE THIS REF TO INJECTOR USE `this.injector` _injector, _remoteComponentLoader, _remoteComponentCache, _dynamicBinding, _options) { this._vcr = _vcr; this._injector = _injector; this._remoteComponentLoader = _remoteComponentLoader; this._remoteComponentCache = _remoteComponentCache; this._dynamicBinding = _dynamicBinding; this._options = _options; /** * Custom injector for micro-frontend component. * @default current injector */ this.injector = this._injector; /** * The delay between displaying the contents of the bootloader and the micro-frontend . * * This is to avoid flickering when the micro-frontend loads very quickly. * * @default options.delay, if not set, then 0 */ this.loaderDelay = this._options.loaderDelay ?? 0; this._loader = this._options.loader; this._fallback = this._options.fallback; } ngOnChanges() { return; } ngAfterViewInit() { this.renderMfe(); } ngOnDestroy() { this._clearView(); } /** * Transfer MfeOutletInputs to micro-frontend component. * * Used when changing input "inputs" of this directive. * @internal */ transferInputs() { if (!this._mfeComponentRef) return; this._dynamicBinding.bindInputs(this._mfeComponentRef, this.inputs ?? {}); // Workaround for bug related to Angular and dynamic components. // Link - https://github.com/angular/angular/issues/36667#issuecomment-926526405 this._mfeComponentRef?.injector.get(ChangeDetectorRef).detectChanges(); } /** * Render micro-frontend component. * * While loading bundle of micro-frontend showing loader. * If error occur then showing fallback. * * Used when changing input "mfe" of this directive. * @internal */ async renderMfe() { try { // If some component already rendered then need to unbind outputs if (this._mfeComponentRef) this._dynamicBinding.unbindOutputs(); if (this._remoteComponentCache.isRegistered(this._remoteComponent)) { this._showMfe(); } else { await this._showLoader(); await delay(this.loaderDelay); this._showMfe(); } } catch (error) { console.error(error); this._showFallback(); } } /** * Shows micro-frontend component. * @internal */ async _showMfe() { try { if (this.mfeApp) { this._mfeComponentRef = await this._createView(this._remoteComponent, this.options); this._bindMfeData(); } } catch (error) { console.group(`Error in Microfronted "${this._remoteComponent.app}"`); if (isRemoteComponentWithModule(this._remoteComponent)) { console.log('module :>> ', this._remoteComponent.module); } console.log('component :>> ', this._remoteComponent.component); console.log('is standalone :>> ', isStandaloneRemoteComponent(this._remoteComponent)); console.error(error); console.groupEnd(); this._showFallback(); } } /** * Shows loader content. * @internal */ async _showLoader() { try { if (this._loader) { this._loaderComponentRef = await this._createView(this._loader); } } catch (error) { console.error(error); this._showFallback(); } } /** * Shows fallback content. * @internal */ async _showFallback() { if (this._fallback) { try { this._fallbackComponentRef = await this._createView(this._fallback); } catch (error) { console.error(error); this._clearView(); } } else { this._clearView(); } } async _createView(content, options) { // TemplateRef if (content instanceof TemplateRef) { this._clearView(); return this._vcr.createEmbeddedView(content); } // MFE (Remote Component) else { const componentRef = isRemoteComponentWithModule(content) ? // for modular Angular (any version) components await this._createRemoteComponent(content, options) : // for standalone Angular v13+ components await this._createStandaloneRemoteComponent(content, options); componentRef.changeDetectorRef.detectChanges(); return componentRef; } } // TODO pattern strategy 1 /** * Create view for modular remote component. * @param remoteComponent MFE remote component * @param options (Optional) object of options. */ async _createRemoteComponent(remoteComponent, options) { const { component, ngModuleRef } = await this._remoteComponentLoader.loadComponentWithModule(remoteComponent, this.injector, options); this._clearView(); const componentRef = this._vcr.createComponent(component, { ngModuleRef, injector: this.injector, }); return componentRef; } // TODO pattern strategy 2 /** * Create view for standalone remote component. * @param remoteComponent MFE remote component * @param options (Optional) object of options. */ async _createStandaloneRemoteComponent(remoteComponent, options) { const component = await this._remoteComponentLoader.loadStandaloneComponent(remoteComponent, options); this._clearView(); const componentRef = this._vcr.createComponent(component, { injector: this.injector, }); return componentRef; } // TODO работает и без этого метода, но не работает output /** * Binding the initial data of the micro-frontend. * @internal */ _bindMfeData() { if (!this._mfeComponentRef) { throw new Error(`_bindMfeData method must be called after micro-frontend component "${this.mfeApp}" has been initialized.`); } this._dynamicBinding.bindInputs(this._mfeComponentRef, this.inputs ?? {}); this._dynamicBinding.bindOutputs(this._mfeComponentRef, this.outputs ?? {}); // TODO похоже что не актуально больше работает все и без этой штуки все // Workaround for bug related to Angular and dynamic components. // Link - https://github.com/angular/angular/issues/36667#issuecomment-926526405 this._mfeComponentRef?.injector.get(ChangeDetectorRef).detectChanges(); } /** * Destroy all displayed components and clear view container ref. * @internal */ _clearView() { this._loaderComponentRef?.destroy(); this._fallbackComponentRef?.destroy(); this._mfeComponentRef?.destroy(); this._vcr.clear(); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: MfeOutletDirective, deps: [{ token: i0.ViewContainerRef }, { token: i0.Injector }, { token: RemoteComponentLoader }, { token: RemoteComponentsCache }, { token: DynamicComponentBinding }, { token: NGX_MFE_OPTIONS }], target: i0.ɵɵFactoryTarget.Directive }); } static { this.ɵdir = i0.ɵɵngDeclareDirective({ minVersion: "14.0.0", version: "19.2.5", type: MfeOutletDirective, isStandalone: false, selector: "[mfeOutlet]", inputs: { mfeApp: ["mfeOutlet", "mfeApp"], mfeComponent: ["mfeOutletComponent", "mfeComponent"], mfeModule: ["mfeOutletModule", "mfeModule"], inputs: ["mfeOutletInputs", "inputs"], outputs: ["mfeOutletOutputs", "outputs"], injector: ["mfeOutletInjector", "injector"], loader: ["mfeOutletLoader", "loader"], loaderDelay: ["mfeOutletLoaderDelay", "loaderDelay"], fallback: ["mfeOutletFallback", "fallback"], options: ["mfeOutletOptions", "options"] }, providers: [DynamicComponentBinding], exportAs: ["mfeOutlet"], usesOnChanges: true, ngImport: i0 }); } } __decorate([ TrackChanges('mfeRemote', 'renderMfe', { compare: true, strategy: EChangesStrategy.NonFirst, }), TrackChanges('mfeComponent', 'renderMfe', { compare: true, strategy: EChangesStrategy.NonFirst, }), TrackChanges('mfeModule', 'renderMfe', { compare: true, strategy: EChangesStrategy.NonFirst, }), TrackChanges('inputs', 'transferInputs', { strategy: EChangesStrategy.NonFirst, compare: true, }) ], MfeOutletDirective.prototype, "ngOnChanges", null); i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: MfeOutletDirective, decorators: [{ type: Directive, args: [{ // eslint-disable-next-line @angular-eslint/directive-selector selector: '[mfeOutlet]', exportAs: 'mfeOutlet', providers: [DynamicComponentBinding], standalone: false, }] }], ctorParameters: () => [{ type: i0.ViewContainerRef }, { type: i0.Injector }, { type: RemoteComponentLoader }, { type: RemoteComponentsCache }, { type: DynamicComponentBinding }, { type: undefined, decorators: [{ type: Inject, args: [NGX_MFE_OPTIONS] }] }], propDecorators: { mfeApp: [{ type: Input, args: ['mfeOutlet'] }], mfeComponent: [{ type: Input, args: ['mfeOutletComponent'] }], mfeModule: [{ type: Input, args: ['mfeOutletModule'] }], inputs: [{ type: Input, args: ['mfeOutletInputs'] }], outputs: [{ type: Input, args: ['mfeOutletOutputs'] }], injector: [{ type: Input, args: ['mfeOutletInjector'] }], loader: [{ type: Input, args: ['mfeOutletLoader'] }], loaderDelay: [{ type: Input, args: ['mfeOutletLoaderDelay'] }], fallback: [{ type: Input, args: ['mfeOutletFallback'] }], options: [{ type: Input, args: ['mfeOutletOptions'] }], ngOnChanges: [] } }); /** * Core lib of micro-frontend architecture. * --------------- * * For core module provide MfeModule.forRoot(options). <br/> * * For feature modules provide MfeModule. */ class MfeModule { /** * Sets global configuration of Mfe lib. * @param options Object of options. */ static forRoot(options) { const { preload, mfeConfig } = options; const providers = [ { provide: NGX_MFE_OPTIONS, useValue: options, }, ]; if (isNgxMfeConfigAsync(mfeConfig)) { providers.push({ provide: APP_INITIALIZER, useFactory: () => { return () => { return from(mfeConfig.useLoader(...(mfeConfig.deps ?? []))).pipe(tap((config) => initializeMfeRegistry(config, preload))); }; }, multi: true, }); } else { initializeMfeRegistry(mfeConfig, preload); } return { ngModule: MfeModule, providers }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: MfeModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); } static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.5", ngImport: i0, type: MfeModule, declarations: [MfeOutletDirective], exports: [MfeOutletDirective] }); } static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: MfeModule }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.5", ngImport: i0, type: MfeModule, decorators: [{ type: NgModule, args: [{ declarations: [MfeOutletDirective], exports: [MfeOutletDirective], }] }] }); function initializeMfeRegistry(config, preload) { const mfeRegistry = MfeRegistry.instance; mfeRegistry.setMfeConfig(config); const loadMfeBundle = loadMfeBundleWithMfeRegistry(mfeRegistry); if (preload) { preload.map((mfe) => loadMfeBundle(mfe)); } return mfeRegistry; } /** * Loads micro-frontend app bundle (HOF - High Order Function). * ------ * * Returns function that can load micro-frontend app by provided name. * @param mfeRegistry Registry of micro-frontends apps. */ function loadMfeBundleWithMfeRegistry(mfeRegistry) { return async (mfeString) => { const remoteEntry = await firstValueFrom(mfeRegistry.getMfeRemoteEntry(mfeString)); return loadRemoteEntry({ type: 'module', remoteEntry }); }; } /* * Public API Surface of ngx-mfe */ /** * Generated bundle index. Do not edit. */ export { DynamicComponentBinding, MfeModule, MfeOutletDirective, MfeRegistry, NGX_MFE_OPTIONS, RemoteComponentLoader, RemoteComponentsCache, delay, isNgxMfeConfigAsync, isRemoteComponentWithModule, isStandaloneRemoteComponent, loadMfe }; //# sourceMappingURL=ngx-mfe.mjs.map