Skip to content

Commit

Permalink
refactor(docs-infra): update loading of custom elements to use dynami…
Browse files Browse the repository at this point in the history
…c import syntax (#30704)

Removes the usage of `NgModuleFactoryLoader` and string-based imports for lazy loading.

PR Close #30704
  • Loading branch information
brandonroberts authored and mhevery committed May 31, 2019
1 parent 1315d23 commit fcef390
Show file tree
Hide file tree
Showing 5 changed files with 36 additions and 44 deletions.
4 changes: 2 additions & 2 deletions aio/scripts/_payload-limits.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
"aio": {
"master": {
"uncompressed": {
"runtime-es5": 2980,
"runtime-es2015": 2986,
"runtime-es5": 2516,
"runtime-es2015": 2522,
"main-es5": 504760,
"main-es2015": 443497,
"polyfills-es5": 128751,
Expand Down
10 changes: 5 additions & 5 deletions aio/src/app/custom-elements/custom-elements.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ import { NgModule, NgModuleFactoryLoader, SystemJsNgModuleLoader } from '@angula
import { ROUTES} from '@angular/router';
import { ElementsLoader } from './elements-loader';
import {
ELEMENT_MODULE_PATHS,
ELEMENT_MODULE_PATHS_AS_ROUTES,
ELEMENT_MODULE_PATHS_TOKEN
ELEMENT_MODULE_LOAD_CALLBACKS,
ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES,
ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN
} from './element-registry';
import { LazyCustomElementComponent } from './lazy-custom-element.component';

Expand All @@ -14,12 +14,12 @@ import { LazyCustomElementComponent } from './lazy-custom-element.component';
providers: [
ElementsLoader,
{ provide: NgModuleFactoryLoader, useClass: SystemJsNgModuleLoader },
{ provide: ELEMENT_MODULE_PATHS_TOKEN, useValue: ELEMENT_MODULE_PATHS },
{ 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
// registered as lazy-loadable.
// TODO(andrewjs): Provide first-class support for providing this.
{ provide: ROUTES, useValue: ELEMENT_MODULE_PATHS_AS_ROUTES, multi: true },
{ provide: ROUTES, useValue: ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES, multi: true },
],
})
export class CustomElementsModule { }
29 changes: 15 additions & 14 deletions aio/src/app/custom-elements/element-registry.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
import { InjectionToken, Type } from '@angular/core';
import { LoadChildrenCallback } from '@angular/router';

// Modules containing custom elements must be set up as lazy-loaded routes (loadChildren)
// TODO(andrewjs): This is a hack, Angular should have first-class support for preparing a module
// that contains custom elements.
export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
export const ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES = [
{
selector: 'aio-announcement-bar',
loadChildren: './announcement-bar/announcement-bar.module#AnnouncementBarModule'
loadChildren: () => import('./announcement-bar/announcement-bar.module').then(mod => mod.AnnouncementBarModule)
},
{
selector: 'aio-api-list',
loadChildren: './api/api-list.module#ApiListModule'
loadChildren: () => import('./api/api-list.module').then(mod => mod.ApiListModule)
},
{
selector: 'aio-contributor-list',
loadChildren: './contributor/contributor-list.module#ContributorListModule'
loadChildren: () => import('./contributor/contributor-list.module').then(mod => mod.ContributorListModule)
},
{
selector: 'aio-file-not-found-search',
loadChildren: './search/file-not-found-search.module#FileNotFoundSearchModule'
loadChildren: () => import('./search/file-not-found-search.module').then(mod => mod.FileNotFoundSearchModule)
},
{
selector: 'aio-resource-list',
loadChildren: './resource/resource-list.module#ResourceListModule'
loadChildren: () => import('./resource/resource-list.module').then(mod => mod.ResourceListModule)
},
{
selector: 'aio-toc',
loadChildren: './toc/toc.module#TocModule'
loadChildren: () => import('./toc/toc.module').then(mod => mod.TocModule)
},
{
selector: 'code-example',
loadChildren: './code/code-example.module#CodeExampleModule'
loadChildren: () => import('./code/code-example.module').then(mod => mod.CodeExampleModule)
},
{
selector: 'code-tabs',
loadChildren: './code/code-tabs.module#CodeTabsModule'
loadChildren: () => import('./code/code-tabs.module').then(mod => mod.CodeTabsModule)
},
{
selector: 'live-example',
loadChildren: './live-example/live-example.module#LiveExampleModule'
loadChildren: () => import('./live-example/live-example.module').then(mod => mod.LiveExampleModule)
}
];

Expand All @@ -51,10 +52,10 @@ export interface WithCustomElementComponent {
}

/** Injection token to provide the element path modules. */
export const ELEMENT_MODULE_PATHS_TOKEN = new InjectionToken('aio/elements-map');
export const ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN = new InjectionToken<Map<string, LoadChildrenCallback>>('aio/elements-map');

/** Map of possible custom element selectors to their lazy-loadable module paths. */
export const ELEMENT_MODULE_PATHS = new Map<string, string>();
ELEMENT_MODULE_PATHS_AS_ROUTES.forEach(route => {
ELEMENT_MODULE_PATHS.set(route.selector, route.loadChildren);
export const ELEMENT_MODULE_LOAD_CALLBACKS = new Map<string, LoadChildrenCallback>();
ELEMENT_MODULE_LOAD_CALLBACKS_AS_ROUTES.forEach(route => {
ELEMENT_MODULE_LOAD_CALLBACKS.set(route.selector, route.loadChildren);
});
20 changes: 6 additions & 14 deletions aio/src/app/custom-elements/elements-loader.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
ComponentFactory,
ComponentFactoryResolver, ComponentRef, Injector, NgModuleFactory, NgModuleFactoryLoader,
ComponentFactoryResolver, ComponentRef, Injector, NgModuleFactory,
NgModuleRef,
Type
} from '@angular/core';
import { TestBed, fakeAsync, flushMicrotasks } from '@angular/core/testing';

import { ElementsLoader } from './elements-loader';
import { ELEMENT_MODULE_PATHS_TOKEN, WithCustomElementComponent } from './element-registry';
import { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, WithCustomElementComponent } from './element-registry';


interface Deferred {
Expand All @@ -22,10 +22,9 @@ describe('ElementsLoader', () => {
const injector = TestBed.configureTestingModule({
providers: [
ElementsLoader,
{ provide: NgModuleFactoryLoader, useClass: FakeModuleFactoryLoader },
{ provide: ELEMENT_MODULE_PATHS_TOKEN, useValue: new Map([
['element-a-selector', 'element-a-module-path'],
['element-b-selector', 'element-b-module-path']
{ provide: ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, useValue: new Map<string, () => Promise<NgModuleFactory<WithCustomElementComponent>>>([
['element-a-selector', () => Promise.resolve(new FakeModuleFactory('element-a-module'))],
['element-b-selector', () => Promise.resolve(new FakeModuleFactory('element-b-module'))]
])},
]
});
Expand Down Expand Up @@ -148,7 +147,7 @@ describe('ElementsLoader', () => {

// Verify the right component was loaded/registered.
const Ctor = definedSpy.calls.argsFor(0)[1];
expect(Ctor.observedAttributes).toEqual(['element-a-module-path']);
expect(Ctor.observedAttributes).toEqual(['element-a-module']);
}));

it('should wait until the element is defined', fakeAsync(() => {
Expand Down Expand Up @@ -282,13 +281,6 @@ class FakeModuleFactory extends NgModuleFactory<any> {
}
}

class FakeModuleFactoryLoader extends NgModuleFactoryLoader {
load(modulePath: string): Promise<NgModuleFactory<any>> {
const fakeModuleFactory = new FakeModuleFactory(modulePath);
return Promise.resolve(fakeModuleFactory);
}
}

function returnPromisesFromSpy(spy: jasmine.Spy): Deferred[] {
const deferreds: Deferred[] = [];
spy.and.callFake(() => new Promise((resolve, reject) => deferreds.push({resolve, reject})));
Expand Down
17 changes: 8 additions & 9 deletions aio/src/app/custom-elements/elements-loader.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import {
Inject,
Injectable,
NgModuleFactoryLoader,
NgModuleFactory,
NgModuleRef,
} from '@angular/core';
import { ELEMENT_MODULE_PATHS_TOKEN } from './element-registry';
import { ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN, WithCustomElementComponent } from './element-registry';
import { from, Observable, of } from 'rxjs';
import { createCustomElement } from '@angular/elements';
import { LoadChildrenCallback } from '@angular/router';


@Injectable()
export class ElementsLoader {
/** Map of unregistered custom elements and their respective module paths to load. */
private elementsToLoad: Map<string, string>;
private elementsToLoad: Map<string, LoadChildrenCallback>;
/** Map of custom elements that are in the process of being loaded and registered. */
private elementsLoading = new Map<string, Promise<void>>();

constructor(private moduleFactoryLoader: NgModuleFactoryLoader,
private moduleRef: NgModuleRef<any>,
@Inject(ELEMENT_MODULE_PATHS_TOKEN) elementModulePaths: Map<string, string>) {
constructor(private moduleRef: NgModuleRef<any>,
@Inject(ELEMENT_MODULE_LOAD_CALLBACKS_TOKEN) elementModulePaths: Map<string, LoadChildrenCallback>) {
this.elementsToLoad = new Map(elementModulePaths);
}

Expand Down Expand Up @@ -47,9 +47,8 @@ export class ElementsLoader {

if (this.elementsToLoad.has(selector)) {
// Load and register the custom element (for the first time).
const modulePath = this.elementsToLoad.get(selector)!;
const loadedAndRegistered = this.moduleFactoryLoader
.load(modulePath)
const modulePathLoader = this.elementsToLoad.get(selector)!;
const loadedAndRegistered = (modulePathLoader() as Promise<NgModuleFactory<WithCustomElementComponent>>)
.then(elementModuleFactory => {
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
const injector = elementModuleRef.injector;
Expand Down

0 comments on commit fcef390

Please sign in to comment.