Skip to content

Commit dcf8840

Browse files
gkalpakvicb
authored andcommitted
feat(elements): implement registerAsCustomElements()
closes angular#19469
1 parent 60c0b17 commit dcf8840

File tree

4 files changed

+168
-0
lines changed

4 files changed

+168
-0
lines changed

packages/elements/public_api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
*/
1414
export {NgElement, NgElementWithProps} from './src/ng-element';
1515
export {NgElementConstructor} from './src/ng-element-constructor';
16+
export {registerAsCustomElements} from './src/register-as-custom-elements';
1617
export {VERSION} from './src/version';
1718

1819
// This file only reexports content of the `src` folder. Keep it that way.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {NgModuleFactory, NgModuleRef, PlatformRef, Type} from '@angular/core';
10+
11+
import {NgElements} from './ng-elements';
12+
import {isFunction} from './utils';
13+
14+
/**
15+
* TODO(gkalpak): Add docs.
16+
* @experimental
17+
*/
18+
export function registerAsCustomElements<T>(
19+
customElementComponents: Type<any>[], platformRef: PlatformRef,
20+
moduleFactory: NgModuleFactory<T>): Promise<NgModuleRef<T>>;
21+
export function registerAsCustomElements<T>(
22+
customElementComponents: Type<any>[],
23+
bootstrapFn: () => Promise<NgModuleRef<T>>): Promise<NgModuleRef<T>>;
24+
export function registerAsCustomElements<T>(
25+
customElementComponents: Type<any>[],
26+
platformRefOrBootstrapFn: PlatformRef | (() => Promise<NgModuleRef<T>>),
27+
moduleFactory?: NgModuleFactory<T>): Promise<NgModuleRef<T>> {
28+
const bootstrapFn = isFunction(platformRefOrBootstrapFn) ?
29+
platformRefOrBootstrapFn :
30+
() => platformRefOrBootstrapFn.bootstrapModuleFactory(moduleFactory !);
31+
32+
return bootstrapFn().then(moduleRef => {
33+
const ngElements = new NgElements(moduleRef, customElementComponents);
34+
ngElements.register();
35+
return moduleRef;
36+
});
37+
}
Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {CompilerFactory, Component, NgModule, NgModuleFactory, NgModuleRef, PlatformRef, Type, destroyPlatform} from '@angular/core';
10+
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
11+
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
12+
import {NgElementImpl} from '../src/ng-element';
13+
import {registerAsCustomElements} from '../src/register-as-custom-elements';
14+
import {isFunction} from '../src/utils';
15+
import {patchEnv, restoreEnv, supportsCustomElements} from '../testing/index';
16+
17+
type BootstrapFn<M> = () => Promise<NgModuleRef<M>>;
18+
type ArgsWithModuleFactory<M> = [PlatformRef, NgModuleFactory<M>];
19+
type ArgsWithBootstrapFn<M> = [BootstrapFn<M>];
20+
21+
export function main() {
22+
if (!supportsCustomElements()) {
23+
return;
24+
}
25+
26+
describe('registerAsCustomElements()', () => {
27+
const createArgsToRegisterWithModuleFactory = (platformFn: () => PlatformRef) => {
28+
const tempPlatformRef = platformBrowserDynamic();
29+
const compilerFactory = tempPlatformRef.injector.get(CompilerFactory) as CompilerFactory;
30+
const compiler = compilerFactory.createCompiler([]);
31+
tempPlatformRef.destroy();
32+
33+
const platformRef = platformFn();
34+
const moduleFactory = compiler.compileModuleSync(TestModule);
35+
36+
return [platformRef, moduleFactory] as ArgsWithModuleFactory<TestModule>;
37+
};
38+
const createArgsToRegisterWithBootstrapFn =
39+
() => [() => platformBrowserDynamic().bootstrapModule(TestModule)] as
40+
ArgsWithBootstrapFn<TestModule>;
41+
42+
beforeAll(() => patchEnv());
43+
afterAll(() => restoreEnv());
44+
45+
// Run the tests with both an `NgModuleFactory` and a `bootstrapFn()`.
46+
runTests(
47+
'with `NgModuleFactory` (on `platformBrowserDynamic`)',
48+
() => createArgsToRegisterWithModuleFactory(platformBrowserDynamic));
49+
runTests(
50+
'with `NgModuleFactory` (on `platformBrowser`)',
51+
() => createArgsToRegisterWithModuleFactory(platformBrowser));
52+
runTests('with `bootstrapFn()`', createArgsToRegisterWithBootstrapFn);
53+
54+
function runTests<M>(
55+
description: string, createArgs: () => ArgsWithModuleFactory<M>| ArgsWithBootstrapFn<M>) {
56+
describe(description, () => {
57+
const customElementComponents: Type<any>[] = [FooBarComponent, BazQuxComponent];
58+
const hasBootstrapFn = (arr: any[]): arr is ArgsWithBootstrapFn<M> => isFunction(arr[0]);
59+
let doRegister: () => Promise<NgModuleRef<M>>;
60+
let defineSpy: jasmine.Spy;
61+
62+
beforeEach(() => {
63+
destroyPlatform();
64+
65+
const args = createArgs();
66+
doRegister = hasBootstrapFn(args) ?
67+
() => registerAsCustomElements(customElementComponents, args[0]) :
68+
() => registerAsCustomElements(customElementComponents, args[0], args[1]);
69+
70+
defineSpy = spyOn(customElements, 'define');
71+
});
72+
73+
afterEach(() => destroyPlatform());
74+
75+
it('should bootstrap the `NgModule` and return an `NgModuleRef` instance', done => {
76+
doRegister()
77+
.then(ref => expect(ref.instance).toEqual(jasmine.any(TestModule)))
78+
.then(done, done.fail);
79+
});
80+
81+
it('should define a custom element for each component', done => {
82+
doRegister()
83+
.then(() => {
84+
expect(defineSpy).toHaveBeenCalledTimes(2);
85+
expect(defineSpy).toHaveBeenCalledWith('foo-bar', jasmine.any(Function));
86+
expect(defineSpy).toHaveBeenCalledWith('baz-qux', jasmine.any(Function));
87+
88+
expect(defineSpy.calls.argsFor(0)[1]).toEqual(jasmine.objectContaining({
89+
is: 'foo-bar',
90+
observedAttributes: [],
91+
upgrade: jasmine.any(Function),
92+
}));
93+
expect(defineSpy.calls.argsFor(1)[1]).toEqual(jasmine.objectContaining({
94+
is: 'baz-qux',
95+
observedAttributes: [],
96+
upgrade: jasmine.any(Function),
97+
}));
98+
})
99+
.then(done, done.fail);
100+
});
101+
});
102+
}
103+
});
104+
}
105+
106+
@Component({
107+
selector: 'foo-bar',
108+
template: 'FooBar',
109+
})
110+
class FooBarComponent {
111+
}
112+
113+
@Component({
114+
selector: 'baz-qux',
115+
template: 'BazQux',
116+
})
117+
class BazQuxComponent {
118+
}
119+
120+
@NgModule({
121+
imports: [BrowserModule],
122+
declarations: [FooBarComponent, BazQuxComponent],
123+
entryComponents: [FooBarComponent, BazQuxComponent],
124+
})
125+
class TestModule {
126+
ngDoBootstrap() {}
127+
}

tools/public_api_guard/elements/elements.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,8 @@ export interface NgElementConstructor<T, P> {
2323
export declare type NgElementWithProps<T, P> = NgElement<T> & {
2424
[property in keyof
2525

26+
/** @experimental */
27+
export declare function registerAsCustomElements<T>(customElementComponents: Type<any>[], platformRef: PlatformRef, moduleFactory: NgModuleFactory<T>): Promise<NgModuleRef<T>>;
28+
2629
/** @experimental */
2730
export declare const VERSION: Version;

0 commit comments

Comments
 (0)