Skip to content

Commit

Permalink
docs(docs-infra): compile module first for Ivy when loading element m…
Browse files Browse the repository at this point in the history
…odules (#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
  • Loading branch information
brandonroberts authored and mhevery committed May 31, 2019
1 parent fcef390 commit 32886cf
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 7 deletions.
3 changes: 1 addition & 2 deletions 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 {
Expand All @@ -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
Expand Down
27 changes: 24 additions & 3 deletions 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';

Expand All @@ -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()', () => {
Expand Down Expand Up @@ -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);
}));
});
});

Expand Down
22 changes: 20 additions & 2 deletions 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';
Expand All @@ -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);
}

Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 32886cf

Please sign in to comment.