Skip to content

Commit

Permalink
feat(elements): implement registerAsCustomElements()
Browse files Browse the repository at this point in the history
closes #19469
  • Loading branch information
gkalpak authored and vicb committed Nov 2, 2017
1 parent 60c0b17 commit dcf8840
Show file tree
Hide file tree
Showing 4 changed files with 168 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/elements/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
*/
export {NgElement, NgElementWithProps} from './src/ng-element';
export {NgElementConstructor} from './src/ng-element-constructor';
export {registerAsCustomElements} from './src/register-as-custom-elements';
export {VERSION} from './src/version';

// This file only reexports content of the `src` folder. Keep it that way.
37 changes: 37 additions & 0 deletions packages/elements/src/register-as-custom-elements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {NgModuleFactory, NgModuleRef, PlatformRef, Type} from '@angular/core';

import {NgElements} from './ng-elements';
import {isFunction} from './utils';

/**
* TODO(gkalpak): Add docs.
* @experimental
*/
export function registerAsCustomElements<T>(
customElementComponents: Type<any>[], platformRef: PlatformRef,
moduleFactory: NgModuleFactory<T>): Promise<NgModuleRef<T>>;
export function registerAsCustomElements<T>(
customElementComponents: Type<any>[],
bootstrapFn: () => Promise<NgModuleRef<T>>): Promise<NgModuleRef<T>>;
export function registerAsCustomElements<T>(
customElementComponents: Type<any>[],
platformRefOrBootstrapFn: PlatformRef | (() => Promise<NgModuleRef<T>>),
moduleFactory?: NgModuleFactory<T>): Promise<NgModuleRef<T>> {
const bootstrapFn = isFunction(platformRefOrBootstrapFn) ?
platformRefOrBootstrapFn :
() => platformRefOrBootstrapFn.bootstrapModuleFactory(moduleFactory !);

return bootstrapFn().then(moduleRef => {
const ngElements = new NgElements(moduleRef, customElementComponents);
ngElements.register();
return moduleRef;
});
}
127 changes: 127 additions & 0 deletions packages/elements/test/register-as-custom-elements_spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

import {CompilerFactory, Component, NgModule, NgModuleFactory, NgModuleRef, PlatformRef, Type, destroyPlatform} from '@angular/core';
import {BrowserModule, platformBrowser} from '@angular/platform-browser';
import {platformBrowserDynamic} from '@angular/platform-browser-dynamic';
import {NgElementImpl} from '../src/ng-element';
import {registerAsCustomElements} from '../src/register-as-custom-elements';
import {isFunction} from '../src/utils';
import {patchEnv, restoreEnv, supportsCustomElements} from '../testing/index';

type BootstrapFn<M> = () => Promise<NgModuleRef<M>>;
type ArgsWithModuleFactory<M> = [PlatformRef, NgModuleFactory<M>];
type ArgsWithBootstrapFn<M> = [BootstrapFn<M>];

export function main() {
if (!supportsCustomElements()) {
return;
}

describe('registerAsCustomElements()', () => {
const createArgsToRegisterWithModuleFactory = (platformFn: () => PlatformRef) => {
const tempPlatformRef = platformBrowserDynamic();
const compilerFactory = tempPlatformRef.injector.get(CompilerFactory) as CompilerFactory;
const compiler = compilerFactory.createCompiler([]);
tempPlatformRef.destroy();

const platformRef = platformFn();
const moduleFactory = compiler.compileModuleSync(TestModule);

return [platformRef, moduleFactory] as ArgsWithModuleFactory<TestModule>;
};
const createArgsToRegisterWithBootstrapFn =
() => [() => platformBrowserDynamic().bootstrapModule(TestModule)] as
ArgsWithBootstrapFn<TestModule>;

beforeAll(() => patchEnv());
afterAll(() => restoreEnv());

// Run the tests with both an `NgModuleFactory` and a `bootstrapFn()`.
runTests(
'with `NgModuleFactory` (on `platformBrowserDynamic`)',
() => createArgsToRegisterWithModuleFactory(platformBrowserDynamic));
runTests(
'with `NgModuleFactory` (on `platformBrowser`)',
() => createArgsToRegisterWithModuleFactory(platformBrowser));
runTests('with `bootstrapFn()`', createArgsToRegisterWithBootstrapFn);

function runTests<M>(
description: string, createArgs: () => ArgsWithModuleFactory<M>| ArgsWithBootstrapFn<M>) {
describe(description, () => {
const customElementComponents: Type<any>[] = [FooBarComponent, BazQuxComponent];
const hasBootstrapFn = (arr: any[]): arr is ArgsWithBootstrapFn<M> => isFunction(arr[0]);
let doRegister: () => Promise<NgModuleRef<M>>;
let defineSpy: jasmine.Spy;

beforeEach(() => {
destroyPlatform();

const args = createArgs();
doRegister = hasBootstrapFn(args) ?
() => registerAsCustomElements(customElementComponents, args[0]) :
() => registerAsCustomElements(customElementComponents, args[0], args[1]);

defineSpy = spyOn(customElements, 'define');
});

afterEach(() => destroyPlatform());

it('should bootstrap the `NgModule` and return an `NgModuleRef` instance', done => {
doRegister()
.then(ref => expect(ref.instance).toEqual(jasmine.any(TestModule)))
.then(done, done.fail);
});

it('should define a custom element for each component', done => {
doRegister()
.then(() => {
expect(defineSpy).toHaveBeenCalledTimes(2);
expect(defineSpy).toHaveBeenCalledWith('foo-bar', jasmine.any(Function));
expect(defineSpy).toHaveBeenCalledWith('baz-qux', jasmine.any(Function));

expect(defineSpy.calls.argsFor(0)[1]).toEqual(jasmine.objectContaining({
is: 'foo-bar',
observedAttributes: [],
upgrade: jasmine.any(Function),
}));
expect(defineSpy.calls.argsFor(1)[1]).toEqual(jasmine.objectContaining({
is: 'baz-qux',
observedAttributes: [],
upgrade: jasmine.any(Function),
}));
})
.then(done, done.fail);
});
});
}
});
}

@Component({
selector: 'foo-bar',
template: 'FooBar',
})
class FooBarComponent {
}

@Component({
selector: 'baz-qux',
template: 'BazQux',
})
class BazQuxComponent {
}

@NgModule({
imports: [BrowserModule],
declarations: [FooBarComponent, BazQuxComponent],
entryComponents: [FooBarComponent, BazQuxComponent],
})
class TestModule {
ngDoBootstrap() {}
}
3 changes: 3 additions & 0 deletions tools/public_api_guard/elements/elements.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,8 @@ export interface NgElementConstructor<T, P> {
export declare type NgElementWithProps<T, P> = NgElement<T> & {
[property in keyof

/** @experimental */
export declare function registerAsCustomElements<T>(customElementComponents: Type<any>[], platformRef: PlatformRef, moduleFactory: NgModuleFactory<T>): Promise<NgModuleRef<T>>;

/** @experimental */
export declare const VERSION: Version;

0 comments on commit dcf8840

Please sign in to comment.