diff --git a/aio/src/app/custom-elements/custom-elements.module.ts b/aio/src/app/custom-elements/custom-elements.module.ts index 2975720d801f0..064dfb8e40cb9 100644 --- a/aio/src/app/custom-elements/custom-elements.module.ts +++ b/aio/src/app/custom-elements/custom-elements.module.ts @@ -1,4 +1,4 @@ -import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angular/core'; +import { NgModule } from '@angular/core'; import { ROUTES} from '@angular/router'; import { ElementsLoader } from './elements-loader'; import { @@ -13,7 +13,6 @@ import { LazyCustomElementComponent } from './lazy-custom-element.component'; exports: [ LazyCustomElementComponent ], providers: [ ElementsLoader, - { provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader }, { provide: ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, useValue: ELEMENT_MODULE_LOAD_CALLBACKS }, // Providing these routes as a signal to the build system that these modules should be diff --git a/aio/src/app/custom-elements/elements-loader.spec.ts b/aio/src/app/custom-elements/elements-loader.spec.ts index d2ccf2878053a..36185a2067193 100644 --- a/aio/src/app/custom-elements/elements-loader.spec.ts +++ b/aio/src/app/custom-elements/elements-loader.spec.ts @@ -1,8 +1,9 @@ import { + Compiler, ComponentFactory, ComponentFactoryResolver, ComponentRef, Injector, NgModuleFactory, NgModuleRef, - Type + Type, } from '@angular/core'; import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing'; @@ -17,19 +18,25 @@ interface Deferred { describe('ElementsLoader', () => { let elementsLoader: ElementsLoader; + let compiler: Compiler; beforeEach(() => { const injector = TestBed.configureTestingModule({ providers: [ ElementsLoader, - { provide: ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, useValue: new Map Promise>>([ + { + provide: ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, useValue: new Map< + string, () => Promise | Type> + >([ ['element-a-selector', () => Promise.resolve(new FakeModuleFactory('element-a-module'))], - ['element-b-selector', () => Promise.resolve(new FakeModuleFactory('element-b-module'))] + ['element-b-selector', () => Promise.resolve(new FakeModuleFactory('element-b-module'))], + ['element-c-selector', () => Promise.resolve(FakeCustomElementModule)] ])}, ] }); elementsLoader = injector.get(ElementsLoader); + compiler = injector.get(Compiler); }); describe('loadContainedCustomElements()', () => { @@ -221,6 +228,20 @@ describe('ElementsLoader', () => { expect(definedSpy).toHaveBeenCalledTimes(1); }) ); + + it('should be able to load and register an element after compiling its NgModule', fakeAsync(() => { + const compilerSpy = spyOn(compiler, 'compileModuleAsync') + .and.returnValue(Promise.resolve(new FakeModuleFactory('element-c-module'))); + + elementsLoader.loadCustomElement('element-c-selector'); + flushMicrotasks(); + + expect(definedSpy).toHaveBeenCalledTimes(1); + expect(definedSpy).toHaveBeenCalledWith('element-c-selector', jasmine.any(Function)); + + expect(compilerSpy).toHaveBeenCalledTimes(1); + expect(compilerSpy).toHaveBeenCalledWith(FakeCustomElementModule); + })); }); }); diff --git a/aio/src/app/custom-elements/elements-loader.ts b/aio/src/app/custom-elements/elements-loader.ts index 7156164a16dd7..f06894082bb44 100644 --- a/aio/src/app/custom-elements/elements-loader.ts +++ b/aio/src/app/custom-elements/elements-loader.ts @@ -1,8 +1,10 @@ import { + Compiler, Inject, Injectable, NgModuleFactory, NgModuleRef, + Type, } from '@angular/core'; import { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, WithCustomElementComponent } from './element-registry'; import { from, Observable, of } from 'rxjs'; @@ -18,7 +20,8 @@ export class ElementsLoader { private elementsLoading = new Map>(); constructor(private moduleRef: NgModuleRef, - @Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Map) { + @Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Map, + private compiler: Compiler) { this.elementsToLoad = new Map(elementModulePaths); } @@ -48,7 +51,22 @@ export class ElementsLoader { if (this.elementsToLoad.has(selector)) { // Load and register the custom element (for the first time). const modulePathLoader = this.elementsToLoad.get(selector)!; - const loadedAndRegistered = (modulePathLoader() as Promise>) + const loadedAndRegistered = + (modulePathLoader() as Promise | Type>) + .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 { + return this.compiler.compileModuleAsync(elementModuleOrFactory); + } + }) .then(elementModuleFactory => { const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector); const injector = elementModuleRef.injector;