Skip to content
Permalink
Browse files

feat(elements): George's comments (#22413)

PR Close #22413
  • Loading branch information...
andrewseguin authored and mhevery committed Mar 2, 2018
1 parent 1936808 commit 46efd4b93847a8f6369e34efdee1ac4c1d139e25
@@ -40,7 +40,7 @@ filegroup(
"reflect-metadata",
"source-map-support",
"minimist",
"@webcomponents/webcomponentsjs",
"@webcomponents/custom-elements",
"tslib",
] for ext in [
"*.js",
@@ -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,17 +36,16 @@ 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',
templateUrl: './app.component.html',
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);
}
}
@@ -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",
@@ -51,7 +51,7 @@ export const ELEMENT_MODULE_PATHS_AS_ROUTES = [
* a custom element.
*/
export interface WithCustomElementComponent {
customElementComponent: Type<string>;
customElementComponent: Type<any>;
}

/** Injection token to provide the element path modules. */
@@ -23,16 +23,16 @@ class FakeComponentFactory extends ComponentFactory<any> {
create(injector: Injector,
projectableNodes?: any[][],
rootSelectorOrNode?: string | any,
ngModule?: NgModuleRef<any>): ComponentRef<string> {
return jasmine.createSpyObj('ComponentRef', ['methods']);
ngModule?: NgModuleRef<any>): ComponentRef<any> {
return (jasmine.createSpy('ComponentRef') as any) as ComponentRef<any>;
};
}

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<any>;
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

@@ -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));
}
@@ -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 {
@@ -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",
@@ -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",
@@ -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.
@@ -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);
}

@@ -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;
}

/**
@@ -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<P> = {
[property in keyof P]: P[property]
@@ -59,14 +61,14 @@ export interface NgElementConfig {
injector: Injector;
strategyFactory?: NgElementStrategyFactory;
propertyInputs?: string[];
attributeToPropertyInputs?: Map<string, string>;
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<string, string>();
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<P>(
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<P>(

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<P>(
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,
});
@@ -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
@@ -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",

0 comments on commit 46efd4b

Please sign in to comment.
You can’t perform that action at this time.