From 46efd4b93847a8f6369e34efdee1ac4c1d139e25 Mon Sep 17 00:00:00 2001 From: Andrew Seguin Date: Fri, 2 Mar 2018 10:08:16 -0800 Subject: [PATCH] feat(elements): George's comments (#22413) PR Close #22413 --- BUILD.bazel | 2 +- .../guide/{elements.md => custom-elements.md} | 8 +- aio/content/navigation.json | 8 +- .../app/custom-elements/element-registry.ts | 2 +- .../custom-elements/elements-loader.spec.ts | 8 +- .../app/custom-elements/elements-loader.ts | 2 - .../expandable-section.component.ts | 2 +- package.json | 1 - packages/elements/package.json | 3 +- packages/elements/public_api.ts | 2 +- .../src/component-factory-strategy.ts | 19 +-- packages/elements/src/element-strategy.ts | 4 +- .../elements/src/ng-element-constructor.ts | 26 ++-- packages/elements/src/version.ts | 6 - packages/elements/test/BUILD.bazel | 3 +- .../test/component-factory-strategy_spec.ts | 34 +++--- .../test/ng-element-constructor_spec.ts | 20 ++- packages/elements/testing/BUILD.bazel | 19 --- packages/elements/testing/index.ts | 115 ------------------ test-main.js | 2 +- tools/public_api_guard/elements/elements.d.ts | 13 +- yarn.lock | 4 - 22 files changed, 81 insertions(+), 222 deletions(-) rename aio/content/guide/{elements.md => custom-elements.md} (82%) delete mode 100644 packages/elements/testing/BUILD.bazel delete mode 100644 packages/elements/testing/index.ts diff --git a/BUILD.bazel b/BUILD.bazel index 1b5a862bd1..7c0a961109 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -40,7 +40,7 @@ filegroup( "reflect-metadata", "source-map-support", "minimist", - "@webcomponents/webcomponentsjs", + "@webcomponents/custom-elements", "tslib", ] for ext in [ "*.js", diff --git a/aio/content/guide/elements.md b/aio/content/guide/custom-elements.md similarity index 82% rename from aio/content/guide/elements.md rename to aio/content/guide/custom-elements.md index f17ba92ef6..6b02a7f53f 100644 --- a/aio/content/guide/elements.md +++ b/aio/content/guide/custom-elements.md @@ -15,7 +15,6 @@ detection APIs. ```ts //hello-world.ts import { Component, Input, NgModule } from '@angular/core'; -import { createNgElementConstructor, getConfigFromComponentFactory } from '@angular/elements'; @Component({ selector: 'hello-world', @@ -37,7 +36,7 @@ export class HelloWorldModule {} import { Component, NgModuleRef } from '@angular/core'; import { createNgElementConstructor } from '@angular/elements'; -import { HelloWorld } from './hello-world.ngfactory'; +import { HelloWorld } from './hello-world'; @Component({ selector: 'app-root', @@ -45,9 +44,8 @@ import { HelloWorld } from './hello-world.ngfactory'; styleUrls: ['./app.component.css'] }) export class AppComponent { - constructor(ngModuleRef: NgModuleRef) { - const ngElementConfig = getConfigFromComponentFactory(HelloWorld, injector); - const NgElementConstructor = createNgElementConstructor(ngElementConfig); + constructor(injector: Injector) { + const NgElementConstructor = createNgElementConstructor(HelloWorld, {injector}); customElements.register('hello-world', NgElementConstructor); } } diff --git a/aio/content/navigation.json b/aio/content/navigation.json index 938132a7ba..4bbc4b61c8 100644 --- a/aio/content/navigation.json +++ b/aio/content/navigation.json @@ -198,7 +198,7 @@ "url": "guide/structural-directives", "title": "Structural Directives", "tooltip": "Structural directives manipulate the layout of the page." - }, + }, { "url": "guide/pipes", "title": "Pipes", @@ -458,9 +458,9 @@ ] }, { - "url": "guide/elements", - "title": "Elements", - "tooltip": "Exporting Angular Components as Web Components" + "url": "guide/custom-elements", + "title": "Custom Elements", + "tooltip": "Using Angular Components as Custom Elements." }, { "title": "Service Workers", diff --git a/aio/src/app/custom-elements/element-registry.ts b/aio/src/app/custom-elements/element-registry.ts index 869b83f09a..ca7fed36f4 100644 --- a/aio/src/app/custom-elements/element-registry.ts +++ b/aio/src/app/custom-elements/element-registry.ts @@ -51,7 +51,7 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [ * a custom element. */ export interface WithCustomElementComponent { - customElementComponent: Type; + customElementComponent: Type; } /** Injection token to provide the element path modules. */ diff --git a/aio/src/app/custom-elements/elements-loader.spec.ts b/aio/src/app/custom-elements/elements-loader.spec.ts index 1f4028cdf6..064478f4c8 100644 --- a/aio/src/app/custom-elements/elements-loader.spec.ts +++ b/aio/src/app/custom-elements/elements-loader.spec.ts @@ -23,8 +23,8 @@ class FakeComponentFactory extends ComponentFactory { create(injector: Injector, projectableNodes?: any[][], rootSelectorOrNode?: string | any, - ngModule?: NgModuleRef): ComponentRef { - return jasmine.createSpyObj('ComponentRef', ['methods']); + ngModule?: NgModuleRef): ComponentRef { + return (jasmine.createSpy('ComponentRef') as any) as ComponentRef; }; } @@ -32,7 +32,7 @@ const FAKE_COMPONENT_FACTORIES = new Map([ ['element-a-module-path', new FakeComponentFactory('element-a-input')] ]); -describe('ElementsLoader', () => { +fdescribe('ElementsLoader', () => { let elementsLoader: ElementsLoader; let injectedModuleRef: NgModuleRef; let fakeCustomElements; @@ -87,7 +87,7 @@ describe('ElementsLoader', () => { elementsLoader.loadContainingCustomElements(hostEl); tick(); // Tick for the module factory loader's async `load` function - // Call again to to check how many times registerAsCustomElements was called. + // Call again to to check how many times customElements.define was called. elementsLoader.loadContainingCustomElements(hostEl); tick(); // Tick for the module factory loader's async `load` function diff --git a/aio/src/app/custom-elements/elements-loader.ts b/aio/src/app/custom-elements/elements-loader.ts index 7a951f506c..fa61c70a44 100644 --- a/aio/src/app/custom-elements/elements-loader.ts +++ b/aio/src/app/custom-elements/elements-loader.ts @@ -32,8 +32,6 @@ export class ElementsLoader { if (!selectors.length) { return of(null); } - selectors.forEach(s => this.register(s)); - // Returns observable that completes when all discovered elements have been registered. return fromPromise(Promise.all(selectors.map(s => this.register(s))).then(result => null)); } diff --git a/aio/src/app/custom-elements/expandable-section/expandable-section.component.ts b/aio/src/app/custom-elements/expandable-section/expandable-section.component.ts index 403c463513..22dfa4b47f 100644 --- a/aio/src/app/custom-elements/expandable-section/expandable-section.component.ts +++ b/aio/src/app/custom-elements/expandable-section/expandable-section.component.ts @@ -3,7 +3,7 @@ import {Component, Input} from '@angular/core'; /** Custom element wrapper for the material expansion panel with a title input. */ @Component({ - selector: 'expandable-section', + selector: 'aio-expandable-section', templateUrl: 'expandable-section.component.html', }) export class ExpandableSectionComponent { diff --git a/package.json b/package.json index 134bdd241b..e3f3836e99 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,6 @@ "@types/source-map": "^0.5.1", "@types/systemjs": "0.19.32", "@webcomponents/custom-elements": "^1.0.4", - "@webcomponents/webcomponentsjs": "^1.1.0", "angular": "npm:angular@1.6", "angular-1.5": "npm:angular@1.5", "angular-mocks": "npm:angular-mocks@1.6", diff --git a/packages/elements/package.json b/packages/elements/package.json index a03d8d251c..e6a9dfc167 100644 --- a/packages/elements/package.json +++ b/packages/elements/package.json @@ -13,7 +13,8 @@ }, "peerDependencies": { "@angular/core": "0.0.0-PLACEHOLDER", - "@angular/platform-browser": "0.0.0-PLACEHOLDER" + "@angular/platform-browser": "0.0.0-PLACEHOLDER", + "rxjs": "^5.5.0" }, "repository": { "type": "git", diff --git a/packages/elements/public_api.ts b/packages/elements/public_api.ts index 5f72c0d16b..afbb057fa5 100644 --- a/packages/elements/public_api.ts +++ b/packages/elements/public_api.ts @@ -12,7 +12,7 @@ * Entry point for all public APIs of the `elements` package. */ export {NgElementStrategy, NgElementStrategyEvent, NgElementStrategyFactory} from './src/element-strategy'; -export {NgElement, NgElementConfig, NgElementConstructor, createNgElementConstructor} from './src/ng-element-constructor'; +export {NgElement, NgElementConfig, NgElementConstructor, WithProperties, createNgElementConstructor} from './src/ng-element-constructor'; export {VERSION} from './src/version'; // This file only reexports content of the `src` folder. Keep it that way. diff --git a/packages/elements/src/component-factory-strategy.ts b/packages/elements/src/component-factory-strategy.ts index ec443a142a..d66bf7ee71 100644 --- a/packages/elements/src/component-factory-strategy.ts +++ b/packages/elements/src/component-factory-strategy.ts @@ -104,7 +104,7 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { * Returns the component property value. If the component has not yet been created, the value is * retrieved from the cached initialization values. */ - getPropertyValue(property: string): any { + getInputValue(property: string): any { if (!this.componentRef) { return this.initialInputValues.get(property); } @@ -116,8 +116,8 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { * Sets the input value for the property. If the component has not yet been created, the value is * cached and set when the component is created. */ - setPropertyValue(property: string, value: any): void { - if (strictEquals(value, this.getPropertyValue(property))) { + setInputValue(property: string, value: any): void { + if (strictEquals(value, this.getInputValue(property))) { return; } @@ -158,7 +158,7 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { this.componentFactory.inputs.forEach(({propName}) => { const initialValue = this.initialInputValues.get(propName); if (initialValue) { - this.setPropertyValue(propName, initialValue); + this.setInputValue(propName, initialValue); } else { // Keep track of inputs that were not initialized in case we need to know this for // calling ngOnChanges with SimpleChanges @@ -185,8 +185,11 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { return; } - (this.componentRef !.instance as any as OnChanges).ngOnChanges(this.inputChanges); + // Cache the changes and set inputChanges to null to capture any changes that might occur + // during ngOnChanges. + const inputChanges = this.inputChanges; this.inputChanges = null; + (this.componentRef !.instance as any as OnChanges).ngOnChanges(inputChanges); } /** @@ -199,8 +202,8 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { } this.scheduledChangeDetectionFn = scheduler.scheduleBeforeRender(() => { - this.detectChanges(); this.scheduledChangeDetectionFn = null; + this.detectChanges(); }); } @@ -209,7 +212,7 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { */ protected recordInputChange(property: string, currentValue: any): void { // Do not record the change if the component does not implement `OnChanges`. - if (!this.componentRef || !this.implementsOnChanges) { + if (this.componentRef && !this.implementsOnChanges) { return; } @@ -228,7 +231,7 @@ export class ComponentFactoryNgElementStrategy implements NgElementStrategy { const isFirstChange = this.uninitializedInputs.has(property); this.uninitializedInputs.delete(property); - const previousValue = isFirstChange ? undefined : this.getPropertyValue(property); + const previousValue = isFirstChange ? undefined : this.getInputValue(property); this.inputChanges[property] = new SimpleChange(previousValue, currentValue, isFirstChange); } diff --git a/packages/elements/src/element-strategy.ts b/packages/elements/src/element-strategy.ts index c5d8d4bc20..a58ec07704 100644 --- a/packages/elements/src/element-strategy.ts +++ b/packages/elements/src/element-strategy.ts @@ -29,8 +29,8 @@ export interface NgElementStrategy { connect(element: HTMLElement): void; disconnect(): void; - getPropertyValue(propName: string): any; - setPropertyValue(propName: string, value: string): void; + getInputValue(propName: string): any; + setInputValue(propName: string, value: string): void; } /** diff --git a/packages/elements/src/ng-element-constructor.ts b/packages/elements/src/ng-element-constructor.ts index 38cec8c63c..af5095cf2f 100644 --- a/packages/elements/src/ng-element-constructor.ts +++ b/packages/elements/src/ng-element-constructor.ts @@ -42,6 +42,8 @@ export abstract class NgElement extends HTMLElement { /** * Additional type information that can be added to the NgElement class for properties added based * on the inputs and methods of the underlying component. + * + * @experimental */ export type WithProperties

= { [property in keyof P]: P[property] @@ -59,14 +61,14 @@ export interface NgElementConfig { injector: Injector; strategyFactory?: NgElementStrategyFactory; propertyInputs?: string[]; - attributeToPropertyInputs?: Map; + attributeToPropertyInputs?: {[key: string]: string}; } /** Gets a map of default set of attributes to observe and the properties they affect. */ function getDefaultAttributeToPropertyInputs(inputs: {propName: string, templateName: string}[]) { - const attributeToPropertyInputs = new Map(); + const attributeToPropertyInputs: {[key: string]: string} = {}; inputs.forEach(({propName, templateName}) => { - attributeToPropertyInputs.set(camelToDashCase(templateName), propName); + attributeToPropertyInputs[camelToDashCase(templateName)] = propName; }); return attributeToPropertyInputs; @@ -100,7 +102,7 @@ export function createNgElementConstructor

( config.attributeToPropertyInputs || getDefaultAttributeToPropertyInputs(inputs); class NgElementImpl extends NgElement { - static readonly observedAttributes = Array.from(attributeToPropertyInputs.keys()); + static readonly observedAttributes = Object.keys(attributeToPropertyInputs); constructor(strategyFactoryOverride?: NgElementStrategyFactory) { super(); @@ -113,16 +115,16 @@ export function createNgElementConstructor

( attributeChangedCallback( attrName: string, oldValue: string|null, newValue: string, namespace?: string): void { - const propName = attributeToPropertyInputs.get(attrName) !; - this.ngElementStrategy.setPropertyValue(propName, newValue); + const propName = attributeToPropertyInputs[attrName] !; + this.ngElementStrategy.setInputValue(propName, newValue); } connectedCallback(): void { // Take element attribute inputs and set them as inputs on the strategy - attributeToPropertyInputs.forEach((propName, attrName) => { - const value = this.getAttribute(attrName); - if (value) { - this.ngElementStrategy.setPropertyValue(propName, value); + NgElementImpl.observedAttributes.forEach(attrName => { + const propName = attributeToPropertyInputs[attrName] !; + if (this.hasAttribute(attrName)) { + this.ngElementStrategy.setInputValue(propName, this.getAttribute(attrName) !); } }); @@ -150,8 +152,8 @@ export function createNgElementConstructor

( const propertyInputs = config.propertyInputs || inputs.map(({propName}) => propName); propertyInputs.forEach(property => { Object.defineProperty(NgElementImpl.prototype, property, { - get: function() { return this.ngElementStrategy.getPropertyValue(property); }, - set: function(newValue: any) { this.ngElementStrategy.setPropertyValue(property, newValue); }, + get: function() { return this.ngElementStrategy.getInputValue(property); }, + set: function(newValue: any) { this.ngElementStrategy.setInputValue(property, newValue); }, configurable: true, enumerable: true, }); diff --git a/packages/elements/src/version.ts b/packages/elements/src/version.ts index ccdd01cba7..1b56724e3e 100644 --- a/packages/elements/src/version.ts +++ b/packages/elements/src/version.ts @@ -6,12 +6,6 @@ * found in the LICENSE file at https://angular.io/license */ -/** - * @module - * @description - * Entry point for all public APIs of the common package. - */ - import {Version} from '@angular/core'; /** * @experimental diff --git a/packages/elements/test/BUILD.bazel b/packages/elements/test/BUILD.bazel index e6a2893c27..f8b75340b2 100644 --- a/packages/elements/test/BUILD.bazel +++ b/packages/elements/test/BUILD.bazel @@ -12,7 +12,6 @@ ts_library( "//packages/core", "//packages/core/testing", "//packages/elements", - "//packages/elements/testing", "//packages/platform-browser", "//packages/platform-browser-dynamic", "//packages/platform-browser-dynamic/testing", @@ -25,7 +24,7 @@ filegroup( name = "elements_test_bootstrap_scripts", # do not sort srcs = [ - "//:node_modules/@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js", + "//:node_modules/@webcomponents/custom-elements/src/native-shim.js", "//:node_modules/reflect-metadata/Reflect.js", "//:node_modules/zone.js/dist/zone.js", "//:node_modules/zone.js/dist/async-test.js", diff --git a/packages/elements/test/component-factory-strategy_spec.ts b/packages/elements/test/component-factory-strategy_spec.ts index 95ab423d40..35bab98e2d 100644 --- a/packages/elements/test/component-factory-strategy_spec.ts +++ b/packages/elements/test/component-factory-strategy_spec.ts @@ -40,7 +40,7 @@ describe('ComponentFactoryNgElementStrategy', () => { describe('after connected', () => { beforeEach(() => { // Set up an initial value to make sure it is passed to the component - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); strategy.connect(document.createElement('div')); }); @@ -65,7 +65,7 @@ describe('ComponentFactoryNgElementStrategy', () => { }); it('should initialize the component with initial values', () => { - expect(strategy.getPropertyValue('fooFoo')).toBe('fooFoo-1'); + expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); expect(componentRef.instance.fooFoo).toBe('fooFoo-1'); }); @@ -85,15 +85,15 @@ describe('ComponentFactoryNgElementStrategy', () => { describe('when inputs change and not connected', () => { it('should cache the value', () => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); - expect(strategy.getPropertyValue('fooFoo')).toBe('fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); + expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); // Sanity check: componentRef isn't changed since its not even on the strategy expect(componentRef.instance.fooFoo).toBe(undefined); }); it('should not detect changes', fakeAsync(() => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(0); })); @@ -103,16 +103,16 @@ describe('ComponentFactoryNgElementStrategy', () => { beforeEach(() => { strategy.connect(document.createElement('div')); }); it('should be set on the component instance', () => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); expect(componentRef.instance.fooFoo).toBe('fooFoo-1'); - expect(strategy.getPropertyValue('fooFoo')).toBe('fooFoo-1'); + expect(strategy.getInputValue('fooFoo')).toBe('fooFoo-1'); }); it('should detect changes', fakeAsync(() => { // Connect detected changes automatically expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1); - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(2); })); @@ -121,14 +121,14 @@ describe('ComponentFactoryNgElementStrategy', () => { // Connect detected changes automatically expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(1); - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); - strategy.setPropertyValue('barBar', 'barBar-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('barBar', 'barBar-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expect(componentRef.changeDetectorRef.detectChanges).toHaveBeenCalledTimes(2); })); it('should call ngOnChanges', fakeAsync(() => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expectSimpleChanges( componentRef.instance.simpleChanges[0], @@ -136,8 +136,8 @@ describe('ComponentFactoryNgElementStrategy', () => { })); it('should call ngOnChanges once for multiple input changes', fakeAsync(() => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); - strategy.setPropertyValue('barBar', 'barBar-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('barBar', 'barBar-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expectSimpleChanges(componentRef.instance.simpleChanges[0], { fooFoo: new SimpleChange(undefined, 'fooFoo-1', true), @@ -147,16 +147,16 @@ describe('ComponentFactoryNgElementStrategy', () => { it('should call ngOnChanges twice for changes in different rounds with previous values', fakeAsync(() => { - strategy.setPropertyValue('fooFoo', 'fooFoo-1'); - strategy.setPropertyValue('barBar', 'barBar-1'); + strategy.setInputValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('barBar', 'barBar-1'); tick(16); // scheduler waits 16ms if RAF is unavailable expectSimpleChanges(componentRef.instance.simpleChanges[0], { fooFoo: new SimpleChange(undefined, 'fooFoo-1', true), barBar: new SimpleChange(undefined, 'barBar-1', true) }); - strategy.setPropertyValue('fooFoo', 'fooFoo-2'); - strategy.setPropertyValue('barBar', 'barBar-2'); + strategy.setInputValue('fooFoo', 'fooFoo-2'); + strategy.setInputValue('barBar', 'barBar-2'); tick(16); // scheduler waits 16ms if RAF is unavailable expectSimpleChanges(componentRef.instance.simpleChanges[1], { fooFoo: new SimpleChange('fooFoo-1', 'fooFoo-2', false), diff --git a/packages/elements/test/ng-element-constructor_spec.ts b/packages/elements/test/ng-element-constructor_spec.ts index adeb25783c..837e9e4117 100644 --- a/packages/elements/test/ng-element-constructor_spec.ts +++ b/packages/elements/test/ng-element-constructor_spec.ts @@ -13,7 +13,6 @@ import {Subject} from 'rxjs/Subject'; import {NgElementStrategy, NgElementStrategyEvent, NgElementStrategyFactory} from '../src/element-strategy'; import {NgElementConstructor, createNgElementConstructor} from '../src/ng-element-constructor'; -import {patchEnv, restoreEnv} from '../testing/index'; type WithFooBar = { fooFoo: string, @@ -27,7 +26,6 @@ if (typeof customElements !== 'undefined') { let strategyFactory: TestStrategyFactory; let injector: Injector; - beforeAll(() => patchEnv()); beforeAll(done => { destroyPlatform(); platformBrowserDynamic() @@ -47,7 +45,6 @@ if (typeof customElements !== 'undefined') { }); afterAll(() => destroyPlatform()); - afterAll(() => restoreEnv()); it('should use a default strategy for converting component inputs', () => { expect(NgElementCtor.observedAttributes).toEqual(['foo-foo', 'barbar']); @@ -60,8 +57,8 @@ if (typeof customElements !== 'undefined') { element.connectedCallback(); expect(strategy.connectedElement).toBe(element); - expect(strategy.getPropertyValue('fooFoo')).toBe('value-foo-foo'); - expect(strategy.getPropertyValue('barBar')).toBe('value-barbar'); + expect(strategy.getInputValue('fooFoo')).toBe('value-foo-foo'); + expect(strategy.getInputValue('barBar')).toBe('value-barbar'); }); it('should listen to output events after connected', () => { @@ -108,8 +105,7 @@ if (typeof customElements !== 'undefined') { injector, strategyFactory, propertyInputs: ['prop1', 'prop2'], - attributeToPropertyInputs: - new Map([['attr-1', 'prop1'], ['attr-2', 'prop2']]) + attributeToPropertyInputs: {'attr-1': 'prop1', 'attr-2': 'prop2'} }); customElements.define('test-element-with-changed-attributes', NgElementCtorWithChangedAttr); @@ -128,9 +124,9 @@ if (typeof customElements !== 'undefined') { element.setAttribute('attr-3', 'value-3'); // Made-up attribute element.connectedCallback(); - expect(strategy.getPropertyValue('prop1')).toBe('value-1'); - expect(strategy.getPropertyValue('prop2')).toBe('value-2'); - expect(strategy.getPropertyValue('prop3')).not.toBe('value-3'); + expect(strategy.getInputValue('prop1')).toBe('value-1'); + expect(strategy.getInputValue('prop2')).toBe('value-2'); + expect(strategy.getInputValue('prop3')).not.toBe('value-3'); }); }); }); @@ -169,9 +165,9 @@ export class TestStrategy implements NgElementStrategy { disconnect(): void { this.disconnectCalled = true; } - getPropertyValue(propName: string): any { return this.inputs.get(propName); } + getInputValue(propName: string): any { return this.inputs.get(propName); } - setPropertyValue(propName: string, value: string): void { this.inputs.set(propName, value); } + setInputValue(propName: string, value: string): void { this.inputs.set(propName, value); } } export class TestStrategyFactory implements NgElementStrategyFactory { diff --git a/packages/elements/testing/BUILD.bazel b/packages/elements/testing/BUILD.bazel deleted file mode 100644 index 41d20c0e92..0000000000 --- a/packages/elements/testing/BUILD.bazel +++ /dev/null @@ -1,19 +0,0 @@ -package(default_visibility = ["//visibility:public"]) - -load("//tools:defaults.bzl", "ts_library") - -ts_library( - name = "testing", - srcs = glob( - [ - "*.ts", - ], - ), - module_name = "@angular/elements/testing", - deps = [ - "//packages/core", - "//packages/elements", - "//packages/platform-browser", - "@rxjs", - ], -) diff --git a/packages/elements/testing/index.ts b/packages/elements/testing/index.ts deleted file mode 100644 index e2d2e48026..0000000000 --- a/packages/elements/testing/index.ts +++ /dev/null @@ -1,115 +0,0 @@ -/** - * @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 {scheduler} from '../src/utils'; - -export interface MockScheduler { - schedule: (typeof scheduler)['schedule']; - scheduleBeforeRender: (typeof scheduler)['scheduleBeforeRender']; -} - -export class AsyncMockScheduler implements MockScheduler { - private uid = 0; - private pendingBeforeRenderCallbacks: ({id: number, cb: () => void})[] = []; - private pendingDelayedCallbacks: ({id: number, cb: () => void, delay: number})[] = []; - - flushBeforeRender(): void { - while (this.pendingBeforeRenderCallbacks.length) { - const cb = this.pendingBeforeRenderCallbacks.shift() !.cb; - cb(); - } - } - - reset(): void { - this.pendingBeforeRenderCallbacks.length = 0; - this.pendingDelayedCallbacks.length = 0; - } - - schedule(cb: () => void, delay: number): () => void { - const id = ++this.uid; - let idx = this.pendingDelayedCallbacks.length; - - for (let i = this.pendingDelayedCallbacks.length - 1; i >= 0; --i) { - if (this.pendingDelayedCallbacks[i].delay <= delay) { - idx = i + 1; - break; - } - } - this.pendingDelayedCallbacks.splice(idx, 0, {id, cb, delay}); - - return () => this.remove(id, this.pendingDelayedCallbacks); - } - - scheduleBeforeRender(cb: () => void): () => void { - const id = ++this.uid; - this.pendingBeforeRenderCallbacks.push({id, cb}); - return () => this.remove(id, this.pendingBeforeRenderCallbacks); - } - - tick(ms: number): void { - this.flushBeforeRender(); - - this.pendingDelayedCallbacks.forEach(item => item.delay -= ms); - this.pendingDelayedCallbacks = this.pendingDelayedCallbacks.filter(item => { - if (item.delay <= 0) { - const cb = item.cb; - cb(); - return false; - } - return true; - }); - } - - private remove(id: number, items: {id: number}[]): void { - for (let i = 0, ii = items.length; i < ii; ++i) { - if (items[i].id === id) { - items.splice(i, 1); - break; - } - } - } -} - -export class SyncMockScheduler implements MockScheduler { - schedule(cb: () => void, delay: number): () => void { - cb(); - return () => undefined; - } - - scheduleBeforeRender(cb: () => void): () => void { - cb(); - return () => undefined; - } -} - -export function installMockScheduler(isSync?: false): AsyncMockScheduler; -export function installMockScheduler(isSync: true): SyncMockScheduler; -export function installMockScheduler(isSync?: boolean): AsyncMockScheduler|SyncMockScheduler { - const mockScheduler = isSync ? new SyncMockScheduler() : new AsyncMockScheduler(); - - Object.keys(scheduler).forEach((method: keyof typeof scheduler) => { - spyOn(scheduler, method).and.callFake(mockScheduler[method].bind(mockScheduler)); - }); - - return mockScheduler; -} - -export function patchEnv() { - // This helper function is defined in `test-main.js`. See there for more details. - // (//window as any).$$patchInnerHtmlProp(); -} - -export function restoreEnv() { - // This helper function is defined in `test-main.js`. See there for more details. - //(window as any).$$restoreInnerHtmlProp(); -} - -export function supportsCustomElements() { - // The browser does not natively support custom elements and is not polyfillable. - return typeof customElements !== 'undefined'; -} diff --git a/test-main.js b/test-main.js index 436b775eb5..ff45a5e77b 100644 --- a/test-main.js +++ b/test-main.js @@ -76,7 +76,7 @@ Promise .resolve() // Load browser-specific polyfills for custom elements. - .then(function() { return loadCustomElementsPolyfills(); }) + // .then(function() { return loadCustomElementsPolyfills(); }) // Load necessary testing packages. .then(function() { diff --git a/tools/public_api_guard/elements/elements.d.ts b/tools/public_api_guard/elements/elements.d.ts index 58eb3e8b9f..7a3a49d29b 100644 --- a/tools/public_api_guard/elements/elements.d.ts +++ b/tools/public_api_guard/elements/elements.d.ts @@ -12,7 +12,9 @@ export declare abstract class NgElement extends HTMLElement { /** @experimental */ export interface NgElementConfig { - attributeToPropertyInputs?: Map; + attributeToPropertyInputs?: { + [key: string]: string; + }; injector: Injector; propertyInputs?: string[]; strategyFactory?: NgElementStrategyFactory; @@ -29,8 +31,8 @@ export interface NgElementStrategy { events: Observable; connect(element: HTMLElement): void; disconnect(): void; - getPropertyValue(propName: string): any; - setPropertyValue(propName: string, value: string): void; + getInputValue(propName: string): any; + setInputValue(propName: string, value: string): void; } /** @experimental */ @@ -46,3 +48,8 @@ export interface NgElementStrategyFactory { /** @experimental */ export declare const VERSION: Version; + +/** @experimental */ +export declare type WithProperties

= { + [property in keyof P]: P[property]; +}; diff --git a/yarn.lock b/yarn.lock index dca3efb0ef..e60b2d5f8f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -160,10 +160,6 @@ version "1.0.8" resolved "https://registry.yarnpkg.com/@webcomponents/custom-elements/-/custom-elements-1.0.8.tgz#b7b8ef7248f7681d1ad4286a0ada5fe3c2bc7228" -"@webcomponents/webcomponentsjs@^1.1.0": - version "1.1.0" - resolved "https://registry.yarnpkg.com/@webcomponents/webcomponentsjs/-/webcomponentsjs-1.1.0.tgz#1392799c266fca142622a720176f688beb74d181" - Base64@~0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/Base64/-/Base64-0.2.1.tgz#ba3a4230708e186705065e66babdd4c35cf60028"