diff --git a/packages/core/src/render3/definition.ts b/packages/core/src/render3/definition.ts index 9f57886967..610d651278 100644 --- a/packages/core/src/render3/definition.ts +++ b/packages/core/src/render3/definition.ts @@ -15,7 +15,7 @@ import {Type} from '../type'; import {resolveRendererType2} from '../view/util'; import {diPublic} from './di'; -import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, PipeDef, PipeType} from './interfaces/definition'; +import {ComponentDef, ComponentDefArgs, DirectiveDef, DirectiveDefArgs, DirectiveDefFeature, PipeDef} from './interfaces/definition'; @@ -45,7 +45,6 @@ export function defineComponent(componentDefinition: ComponentDefArgs): Co h: componentDefinition.hostBindings || noop, attributes: componentDefinition.attributes || null, inputs: invertObject(componentDefinition.inputs), - inputsPropertyName: componentDefinition.inputsPropertyName || null, outputs: invertObject(componentDefinition.outputs), rendererType: resolveRendererType2(componentDefinition.rendererType) || null, exportAs: componentDefinition.exportAs, @@ -72,48 +71,73 @@ type OnChangesExpando = OnChanges & { [key: string]: any; }; -export function NgOnChangesFeature(definition: DirectiveDef): void { - const inputs = definition.inputs; - const proto = definition.type.prototype; - const inputsPropertyName = definition.inputsPropertyName; - // Place where we will store SimpleChanges if there is a change - Object.defineProperty(proto, PRIVATE_PREFIX, {value: undefined, writable: true}); - for (let pubKey in inputs) { - const minKey = inputs[pubKey]; - const propertyName = inputsPropertyName && inputsPropertyName[minKey] || pubKey; - const privateMinKey = PRIVATE_PREFIX + minKey; - // Create a place where the actual value will be stored and make it non-enumerable - Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true}); +/** + * Creates an NgOnChangesFeature function for a component's features list. + * + * It accepts an optional map of minified input property names to original property names, + * if any input properties have a public alias. + * + * The NgOnChangesFeature function that is returned decorates a component with support for + * the ngOnChanges lifecycle hook, so it should be included in any component that implements + * that hook. + * + * Example usage: + * + * ``` + * static ngComponentDef = defineComponent({ + * ... + * inputs: {name: 'publicName'}, + * features: [NgOnChangesFeature({name: 'name'})] + * }); + * ``` + * + * @param inputPropertyNames Map of input property names, if they are aliased + * @returns DirectiveDefFeature + */ +export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string}): + DirectiveDefFeature { + return function(definition: DirectiveDef): void { + const inputs = definition.inputs; + const proto = definition.type.prototype; + // Place where we will store SimpleChanges if there is a change + Object.defineProperty(proto, PRIVATE_PREFIX, {value: undefined, writable: true}); + for (let pubKey in inputs) { + const minKey = inputs[pubKey]; + const propertyName = inputPropertyNames && inputPropertyNames[minKey] || pubKey; + const privateMinKey = PRIVATE_PREFIX + minKey; + // Create a place where the actual value will be stored and make it non-enumerable + Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true}); - const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey); + const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey); - // create a getter and setter for property - Object.defineProperty(proto, minKey, { - get: function(this: OnChangesExpando) { - return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) : - this[privateMinKey]; - }, - set: function(this: OnChangesExpando, value: any) { - let simpleChanges = this[PRIVATE_PREFIX]; - let isFirstChange = simpleChanges === undefined; - if (simpleChanges == null) { - simpleChanges = this[PRIVATE_PREFIX] = {}; + // create a getter and setter for property + Object.defineProperty(proto, minKey, { + get: function(this: OnChangesExpando) { + return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) : + this[privateMinKey]; + }, + set: function(this: OnChangesExpando, value: any) { + let simpleChanges = this[PRIVATE_PREFIX]; + let isFirstChange = simpleChanges === undefined; + if (simpleChanges == null) { + simpleChanges = this[PRIVATE_PREFIX] = {}; + } + simpleChanges[propertyName] = new SimpleChange(this[privateMinKey], value, isFirstChange); + (existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) : + this[privateMinKey] = value; } - simpleChanges[propertyName] = new SimpleChange(this[privateMinKey], value, isFirstChange); - (existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) : - this[privateMinKey] = value; - } - }); - } + }); + } - // If an onInit hook is defined, it will need to wrap the ngOnChanges call - // so the call order is changes-init-check in creation mode. In subsequent - // change detection runs, only the check wrapper will be called. - if (definition.onInit != null) { - definition.onInit = onChangesWrapper(definition.onInit); - } + // If an onInit hook is defined, it will need to wrap the ngOnChanges call + // so the call order is changes-init-check in creation mode. In subsequent + // change detection runs, only the check wrapper will be called. + if (definition.onInit != null) { + definition.onInit = onChangesWrapper(definition.onInit); + } - definition.doCheck = onChangesWrapper(definition.doCheck); + definition.doCheck = onChangesWrapper(definition.doCheck); + }; function onChangesWrapper(delegateHook: (() => void) | null) { return function(this: OnChangesExpando) { diff --git a/packages/core/src/render3/interfaces/definition.ts b/packages/core/src/render3/interfaces/definition.ts index 8ebac49f8b..8fa1c783a0 100644 --- a/packages/core/src/render3/interfaces/definition.ts +++ b/packages/core/src/render3/interfaces/definition.ts @@ -68,14 +68,6 @@ export interface DirectiveDef { */ readonly inputs: {[P in keyof T]: P}; - /** - * A dictionary mapping the inputs' minified property names to the original unminified property - * names. - * - * An entry is added if and only if the alias is different from the property name. - */ - readonly inputsPropertyName: {[P in keyof T]: P}; - /** * A dictionary mapping the outputs' minified property names to their public API names, which * are their aliases if any, or their original unminified property names @@ -240,11 +232,6 @@ export interface DirectiveDefArgs { */ inputs?: {[P in keyof T]?: string}; - /** - * TODO: Remove per https://github.com/angular/angular/issues/22591 - */ - inputsPropertyName?: {[P in keyof T]?: string}; - /** * A map of output names. * diff --git a/packages/core/test/render3/common_with_def.ts b/packages/core/test/render3/common_with_def.ts index ca75a201b3..15d6c4cb0f 100644 --- a/packages/core/test/render3/common_with_def.ts +++ b/packages/core/test/render3/common_with_def.ts @@ -19,7 +19,7 @@ NgForOf.ngDirectiveDef = defineDirective({ factory: () => new NgForOfDef( injectViewContainerRef(), injectTemplateRef(), directiveInject(IterableDiffers, InjectFlags.Default, defaultIterableDiffers)), - features: [NgOnChangesFeature], + features: [NgOnChangesFeature()], inputs: { ngForOf: 'ngForOf', ngForTrackBy: 'ngForTrackBy', diff --git a/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts index 1296e9e93f..9cbfa75f00 100644 --- a/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts +++ b/packages/core/test/render3/compiler_canonical/life_cycle_spec.ts @@ -45,8 +45,7 @@ describe('lifecycle hooks', () => { factory: function LifecycleComp_Factory() { return new LifecycleComp(); }, template: function LifecycleComp_Template(ctx: $LifecycleComp$, cm: $boolean$) {}, inputs: {nameMin: 'name'}, - inputsPropertyName: {nameMin: 'nameMin'}, - features: [$r3$.ɵNgOnChangesFeature] + features: [$r3$.ɵNgOnChangesFeature({nameMin: 'nameMin'})] }); // /NORMATIVE } diff --git a/packages/core/test/render3/define_spec.ts b/packages/core/test/render3/define_spec.ts index b26e91f531..6a1599852a 100644 --- a/packages/core/test/render3/define_spec.ts +++ b/packages/core/test/render3/define_spec.ts @@ -30,7 +30,7 @@ describe('define', () => { static ngDirectiveDef = defineDirective({ type: MyDirective, factory: () => new MyDirective(), - features: [NgOnChangesFeature], + features: [NgOnChangesFeature()], inputs: {valA: 'valA', valB: 'valB'} }); } diff --git a/packages/core/test/render3/di_spec.ts b/packages/core/test/render3/di_spec.ts index 7f3414ffde..a513f24082 100644 --- a/packages/core/test/render3/di_spec.ts +++ b/packages/core/test/render3/di_spec.ts @@ -420,7 +420,7 @@ describe('di', () => { type: IfDirective, factory: () => new IfDirective(injectTemplateRef(), injectViewContainerRef()), inputs: {myIf: 'myIf'}, - features: [PublicFeature, NgOnChangesFeature] + features: [PublicFeature, NgOnChangesFeature()] }); } diff --git a/packages/core/test/render3/lifecycle_spec.ts b/packages/core/test/render3/lifecycle_spec.ts index 631315fee7..7321489814 100644 --- a/packages/core/test/render3/lifecycle_spec.ts +++ b/packages/core/test/render3/lifecycle_spec.ts @@ -1987,9 +1987,8 @@ describe('lifecycles', () => { type: Component, tag: name, factory: () => new Component(), - features: [NgOnChangesFeature], - inputs: {a: 'val1', b: 'publicName'}, - inputsPropertyName: {b: 'val2'}, template + features: [NgOnChangesFeature({b: 'val2'})], + inputs: {a: 'val1', b: 'publicName'}, template }); }; } @@ -2007,9 +2006,8 @@ describe('lifecycles', () => { static ngDirectiveDef = defineDirective({ type: Directive, factory: () => new Directive(), - features: [NgOnChangesFeature], - inputs: {a: 'val1', b: 'publicName'}, - inputsPropertyName: {b: 'val2'} + features: [NgOnChangesFeature({b: 'val2'})], + inputs: {a: 'val1', b: 'publicName'} }); } @@ -2397,7 +2395,7 @@ describe('lifecycles', () => { tag: name, factory: () => new Component(), inputs: {val: 'val'}, template, - features: [NgOnChangesFeature] + features: [NgOnChangesFeature()] }); }; }