angular/aio/src/app/custom-elements/elements-loader.ts

83 lines
3.5 KiB
TypeScript

import {
Inject,
Injectable,
NgModuleFactoryLoader,
NgModuleRef,
} from '@angular/core';
import { ELEMENT_MODULE_PATHS_TOKEN } from './element-registry';
import { from as fromPromise, Observable, of } from 'rxjs';
import { createCustomElement } from '@angular/elements';
@Injectable()
export class ElementsLoader {
/** Map of unregistered custom elements and their respective module paths to load. */
private elementsToLoad: Map<string, string>;
/** 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>) {
this.elementsToLoad = new Map(elementModulePaths);
}
/**
* Queries the provided element for any custom elements that have not yet been registered with
* the browser. Custom elements that are registered will be removed from the list of unregistered
* elements so that they will not be queried in subsequent calls.
*/
loadContainedCustomElements(element: HTMLElement): Observable<void> {
const unregisteredSelectors = Array.from(this.elementsToLoad.keys())
.filter(s => element.querySelector(s));
if (!unregisteredSelectors.length) { return of(undefined); }
// Returns observable that completes when all discovered elements have been registered.
const allRegistered = Promise.all(unregisteredSelectors.map(s => this.loadCustomElement(s)));
return fromPromise(allRegistered.then(() => undefined));
}
/** Loads and registers the custom element defined on the `WithCustomElement` module factory. */
loadCustomElement(selector: string): Promise<void> {
if (this.elementsLoading.has(selector)) {
// The custom element is in the process of being loaded and registered.
return this.elementsLoading.get(selector)!;
}
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)
.then(elementModuleFactory => {
const elementModuleRef = elementModuleFactory.create(this.moduleRef.injector);
const injector = elementModuleRef.injector;
const CustomElementComponent = elementModuleRef.instance.customElementComponent;
const CustomElement = createCustomElement(CustomElementComponent, {injector});
customElements!.define(selector, CustomElement);
return customElements.whenDefined(selector);
})
.then(() => {
// The custom element has been successfully loaded and registered.
// Remove from `elementsLoading` and `elementsToLoad`.
this.elementsLoading.delete(selector);
this.elementsToLoad.delete(selector);
})
.catch(err => {
// The custom element has failed to load and register.
// Remove from `elementsLoading`.
// (Do not remove from `elementsToLoad` in case it was a temporary error.)
this.elementsLoading.delete(selector);
return Promise.reject(err);
});
this.elementsLoading.set(selector, loadedAndRegistered);
return loadedAndRegistered;
}
// The custom element has already been loaded and registered.
return Promise.resolve();
}
}