import { Injectable , NgModuleFactoryLoader , Injector , Inject , NgModuleRef , Compiler , NgModuleFactory , Type , ChangeDetectorRef } from '@angular/core' ; import { createCustomElement } from '@angular/elements' ; import { LazyComponentDef , LAZY_CMPS_PATH_TOKEN } from './tokens' ; import { Observable , of , from } from 'rxjs' ; import { LazyCmpLoadedEvent } from './lazy-cmp-loaded-event' ; import { LoadChildrenCallback } from '@angular/router' ; @ Injectable ( { providedIn : 'root' } ) export class ComponentLoaderService { private componentsToLoad : Map < string , LazyComponentDef > ; private loadedCmps = new Map < string , NgModuleRef < any > > ( ) ; private elementsLoading = new Map < string , Promise < LazyCmpLoadedEvent > > ( ) ; constructor ( private injector , @ Inject ( LAZY_CMPS_PATH_TOKEN ) elementModulePaths , private compiler ) { const ELEMENT_MODULE_PATHS = new Map < string , any > ( ) ; elementModulePaths . forEach ( route => { ELEMENT_MODULE_PATHS . set ( route . selector , route ) ; } ) ; this . componentsToLoad = ELEMENT_MODULE_PATHS ; } getComponentsToLoad ( ) { return this . componentsToLoad . keys ( ) ; } /**
     * Heavily inspired by the Angular elements loader on the official repo
     */ loadContainedCustomElements ( tags ) { const unregisteredSelectors = Array . from ( this . componentsToLoad . keys ( ) ) . filter ( s => tags . find ( x => x . toLowerCase ( ) === s . toLowerCase ( ) ) ) ; const alreadyRegistered = Array . from ( this . loadedCmps . keys ( ) ) . filter ( s => tags . find ( x => x . toLowerCase ( ) === s . toLowerCase ( ) ) ) ; unregisteredSelectors . push ( ... alreadyRegistered ) ; const allRegistered = Promise . all ( unregisteredSelectors . map ( s => this . loadComponent ( s , false ) ) ) ; return from ( allRegistered ) ; } param componentTag param createInstance loadComponent ( componentTag , createInstance = true ) { if ( this . elementsLoading . has ( componentTag ) ) { return this . elementsLoading . get ( componentTag ) ; } if ( this . componentsToLoad . has ( componentTag ) ) { const cmpRegistryEntry = this . componentsToLoad . get ( componentTag ) ; const path = cmpRegistryEntry . loadChildren ; const loadPromise = new Promise < LazyCmpLoadedEvent > ( ( resolve , reject ) => { ( path ( ) as Promise < NgModuleFactory < any > | Type < any > > ) . then ( elementModuleOrFactory => { /**
                     * With View Engine, the NgModule factory is created and provided when loaded.
                     * With Ivy, only the NgModule class is provided loaded and must be compiled.
                     * This uses the same mechanism as the deprecated `SystemJsNgModuleLoader` in
                     * in `packages/core/src/linker/system_js_ng_module_factory_loader.ts`
                     * to pass on the NgModuleFactory, or compile the NgModule and return its NgModuleFactory.
                     */ if ( elementModuleOrFactory instanceof NgModuleFactory ) { return elementModuleOrFactory ; } else { try { return this . compiler . compileModuleAsync ( elementModuleOrFactory ) ; } catch ( err ) { reject ( err ) ; throw err ; } } } ) . then ( moduleFactory => { try { const elementModuleRef = moduleFactory . create ( this . injector ) ; const injector = elementModuleRef . injector ; let customElementComponent ; if ( typeof elementModuleRef . instance . customElementComponent === 'object' ) { customElementComponent = elementModuleRef . instance . customElementComponent [ componentTag ] ; if ( ! customElementComponent ) { throw `You specified multiple component elements in module ${ elementModuleRef } but there was no match for tag ${ componentTag } in ${ JSON . stringify ( elementModuleRef . instance . customElementComponent ) }. Make sure the selector in the module is aligned with the one specified in the lazy module definition.` ; } } else { customElementComponent = elementModuleRef . instance . customElementComponent ; } const CustomElement = createCustomElement ( customElementComponent , { injector } ) ; customElements ! . define ( componentTag , CustomElement ) ; customElements . whenDefined ( componentTag ) . then ( ( ) => { this . loadedCmps . set ( componentTag , elementModuleRef ) ; const componentInstance = createInstance ? document . createElement ( componentTag ) : null ; resolve ( { selector : componentTag , componentInstance } ) ; } ) . then ( ( ) => { this . elementsLoading . delete ( componentTag ) ; this . componentsToLoad . delete ( componentTag ) ; } ) . catch ( err => { this . elementsLoading . delete ( componentTag ) ; return Promise . reject ( err ) ; } ) ; } catch ( err ) { reject ( err ) ; throw err ; } } ) . catch ( err => { this . elementsLoading . delete ( componentTag ) ; return Promise . reject ( err ) ; } ) ; } ) ; this . elementsLoading . set ( componentTag , loadPromise ) ; return loadPromise ; } else if ( this . loadedCmps . has ( componentTag ) ) { return new Promise ( resolve => { resolve ( { selector : componentTag , componentInstance : createInstance ? document . createElement ( componentTag ) : null } ) ; } ) ; } else { throw new Error ( `Unrecognized component "${ componentTag }". Make sure it is registered in the component registry` ) ; } } }