docs(docs-infra): compile module first for Ivy when loading element modules (#30704)

In View Engine, NgModule factories are created for each NgModule and loaded when the module is requested. Ivy doesn't generate the
factories by design and only loads the module class, so it must be compiled after being loaded.

PR Close #30704
This commit is contained in:
Brandon 2019-05-30 11:55:59 -05:00 committed by Miško Hevery
parent fcef39048a
commit 32886cf9ac
3 changed files with 45 additions and 7 deletions

View File

@ -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

View File

@ -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<string, () => Promise<NgModuleFactory<WithCustomElementComponent>>>([
{
provide: ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, useValue: new Map<
string, () => Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>
>([
['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);
}));
});
});

View File

@ -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<string, Promise<void>>();
constructor(private moduleRef: NgModuleRef<any>,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Map<string, LoadChildrenCallback>) {
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Map<string, LoadChildrenCallback>,
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<NgModuleFactory<WithCustomElementComponent>>)
const loadedAndRegistered =
(modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent> | Type<WithCustomElementComponent>>)
.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;