Skip to content

Commit a95e819

Browse files
benleshalxhub
authored andcommitted
refactor(ivy): Add newer, smaller NgOnChangesFeature (angular#28187)
PR Close angular#28187
1 parent 5552661 commit a95e819

22 files changed

+801
-1120
lines changed

packages/common/src/directives/ng_template_outlet.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,20 @@ import {Directive, EmbeddedViewRef, Input, OnChanges, SimpleChange, SimpleChange
3434
*/
3535
@Directive({selector: '[ngTemplateOutlet]'})
3636
export class NgTemplateOutlet implements OnChanges {
37-
// TODO(issue/24571): remove '!'.
38-
private _viewRef !: EmbeddedViewRef<any>;
37+
private _viewRef: EmbeddedViewRef<any>|null = null;
3938

40-
// TODO(issue/24571): remove '!'.
41-
@Input() public ngTemplateOutletContext !: Object;
39+
/**
40+
* A context object to attach to the {@link EmbeddedViewRef}. This should be an
41+
* object, the object's keys will be available for binding by the local template `let`
42+
* declarations.
43+
* Using the key `$implicit` in the context object will set its value as default.
44+
*/
45+
@Input() public ngTemplateOutletContext: Object|null = null;
4246

43-
// TODO(issue/24571): remove '!'.
44-
@Input() public ngTemplateOutlet !: TemplateRef<any>;
47+
/**
48+
* A string defining the template reference and optionally the context object for the template.
49+
*/
50+
@Input() public ngTemplateOutlet: TemplateRef<any>|null = null;
4551

4652
constructor(private _viewContainerRef: ViewContainerRef) {}
4753

@@ -97,7 +103,7 @@ export class NgTemplateOutlet implements OnChanges {
97103

98104
private _updateExistingContext(ctx: Object): void {
99105
for (let propName of Object.keys(ctx)) {
100-
(<any>this._viewRef.context)[propName] = (<any>this.ngTemplateOutletContext)[propName];
106+
(<any>this._viewRef !.context)[propName] = (<any>this.ngTemplateOutletContext)[propName];
101107
}
102108
}
103109
}

packages/core/src/change_detection/change_detection_util.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,20 +64,6 @@ export class WrappedValue {
6464
static isWrapped(value: any): value is WrappedValue { return value instanceof WrappedValue; }
6565
}
6666

67-
/**
68-
* Represents a basic change from a previous to a new value.
69-
*
70-
* @publicApi
71-
*/
72-
export class SimpleChange {
73-
constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {}
74-
75-
/**
76-
* Check whether the new value is the first value assigned.
77-
*/
78-
isFirstChange(): boolean { return this.firstChange; }
79-
}
80-
8167
export function isListLikeIterable(obj: any): boolean {
8268
if (!isJsObject(obj)) return false;
8369
return Array.isArray(obj) ||

packages/core/src/interface/lifecycle_hooks.ts

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,9 @@
55
* Use of this source code is governed by an MIT-style license that can be
66
* found in the LICENSE file at https://angular.io/license
77
*/
8-
import {SimpleChanges, SimpleChange} from './simple_change';
8+
import {SimpleChanges} from './simple_change';
99

1010

11-
/**
12-
* Defines an object that associates properties with
13-
* instances of `SimpleChange`.
14-
*
15-
* @see `OnChanges`
16-
*
17-
* @publicApi
18-
*/
19-
export interface SimpleChanges { [propName: string]: SimpleChange; }
20-
2111
/**
2212
* @description
2313
* A lifecycle hook that is called when any data-bound property of a directive changes.

packages/core/src/render3/component.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {assertComponentType} from './assert';
1717
import {getComponentDef} from './definition';
1818
import {diPublicInInjector, getOrCreateNodeInjectorForNode} from './di';
1919
import {publishDefaultGlobalUtils} from './global_utils';
20+
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
2021
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
2122
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
2223
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
@@ -25,7 +26,6 @@ import {RElement, Renderer3, RendererFactory3, domRendererFactory3} from './inte
2526
import {CONTEXT, FLAGS, HEADER_OFFSET, HOST, HOST_NODE, LView, LViewFlags, RootContext, RootContextFlags, TVIEW} from './interfaces/view';
2627
import {enterView, getPreviousOrParentTNode, leaveView, resetComponentState, setCurrentDirectiveDef} from './state';
2728
import {defaultScheduler, getRootView, readPatchedLView, renderStringify} from './util';
28-
import { registerPreOrderHooks, registerPostOrderHooks } from './hooks';
2929

3030

3131

@@ -240,7 +240,8 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
240240
registerPreOrderHooks(dirIndex, def, rootTView);
241241
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
242242
// LNode).
243-
registerPostOrderHooks(rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
243+
registerPostOrderHooks(
244+
rootTView, { directiveStart: dirIndex, directiveEnd: dirIndex + 1 } as TNode);
244245
}
245246

246247
/**

packages/core/src/render3/definition.ts

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,7 @@ export function defineComponent<T>(componentDefinition: {
276276
id: 'c',
277277
styles: componentDefinition.styles || EMPTY_ARRAY,
278278
_: null as never,
279+
setInput: null,
279280
};
280281
def._ = noSideEffects(() => {
281282
const directiveTypes = componentDefinition.directives !;
@@ -380,20 +381,22 @@ export function defineNgModule<T>(def: {type: T} & Partial<NgModuleDef<T>>): nev
380381
*
381382
382383
*/
383-
function invertObject(obj: any, secondary?: any): any {
384-
if (obj == null) return EMPTY_OBJ;
384+
function invertObject<T>(
385+
obj?: {[P in keyof T]?: string | [string, string]},
386+
secondary?: {[key: string]: string}): {[P in keyof T]: string} {
387+
if (obj == null) return EMPTY_OBJ as any;
385388
const newLookup: any = {};
386389
for (const minifiedKey in obj) {
387390
if (obj.hasOwnProperty(minifiedKey)) {
388-
let publicName: string = obj[minifiedKey];
391+
let publicName: string|[string, string] = obj[minifiedKey] !;
389392
let declaredName = publicName;
390393
if (Array.isArray(publicName)) {
391394
declaredName = publicName[1];
392395
publicName = publicName[0];
393396
}
394397
newLookup[publicName] = minifiedKey;
395398
if (secondary) {
396-
(secondary[publicName] = declaredName);
399+
(secondary[publicName] = declaredName as string);
397400
}
398401
}
399402
}
@@ -470,11 +473,11 @@ export function defineBase<T>(baseDefinition: {
470473
*/
471474
outputs?: {[P in keyof T]?: string};
472475
}): BaseDef<T> {
473-
const declaredInputs: {[P in keyof T]: P} = {} as any;
476+
const declaredInputs: {[P in keyof T]: string} = {} as any;
474477
return {
475-
inputs: invertObject(baseDefinition.inputs, declaredInputs),
478+
inputs: invertObject<T>(baseDefinition.inputs as any, declaredInputs),
476479
declaredInputs: declaredInputs,
477-
outputs: invertObject(baseDefinition.outputs),
480+
outputs: invertObject<T>(baseDefinition.outputs as any),
478481
};
479482
}
480483

packages/core/src/render3/features/inherit_definition_feature.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@
77
*/
88

99
import {Type} from '../../interface/type';
10+
import {Component} from '../../metadata/directives';
1011
import {fillProperties} from '../../util/property';
1112
import {EMPTY_ARRAY, EMPTY_OBJ} from '../empty';
1213
import {ComponentDef, DirectiveDef, DirectiveDefFeature, RenderFlags} from '../interfaces/definition';
13-
import { Component } from '../../metadata/directives';
1414

1515

1616

packages/core/src/render3/features/ng_onchanges_feature.ts

Lines changed: 52 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
* found in the LICENSE file at https://angular.io/license
77
*/
88

9-
import {SimpleChange} from '../../change_detection/change_detection_util';
10-
import {SimpleChanges} from '../../interface/simple_change';
119
import {OnChanges} from '../../interface/lifecycle_hooks';
10+
import {SimpleChange, SimpleChanges} from '../../interface/simple_change';
11+
import {EMPTY_OBJ} from '../empty';
1212
import {DirectiveDef, DirectiveDefFeature} from '../interfaces/definition';
1313

1414
const PRIVATE_PREFIX = '__ngOnChanges_';
@@ -40,86 +40,63 @@ type OnChangesExpando = OnChanges & {
4040
* ```
4141
*/
4242
export function NgOnChangesFeature<T>(definition: DirectiveDef<T>): void {
43-
const publicToDeclaredInputs = definition.declaredInputs;
44-
const publicToMinifiedInputs = definition.inputs;
45-
const proto = definition.type.prototype;
46-
for (const publicName in publicToDeclaredInputs) {
47-
if (publicToDeclaredInputs.hasOwnProperty(publicName)) {
48-
const minifiedKey = publicToMinifiedInputs[publicName];
49-
const declaredKey = publicToDeclaredInputs[publicName];
50-
const privateMinKey = PRIVATE_PREFIX + minifiedKey;
51-
52-
// Walk the prototype chain to see if we find a property descriptor
53-
// That way we can honor setters and getters that were inherited.
54-
let originalProperty: PropertyDescriptor|undefined = undefined;
55-
let checkProto = proto;
56-
while (!originalProperty && checkProto &&
57-
Object.getPrototypeOf(checkProto) !== Object.getPrototypeOf(Object.prototype)) {
58-
originalProperty = Object.getOwnPropertyDescriptor(checkProto, minifiedKey);
59-
checkProto = Object.getPrototypeOf(checkProto);
60-
}
61-
62-
const getter = originalProperty && originalProperty.get;
63-
const setter = originalProperty && originalProperty.set;
64-
65-
// create a getter and setter for property
66-
Object.defineProperty(proto, minifiedKey, {
67-
get: getter ||
68-
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }),
69-
set<T>(this: OnChangesExpando, value: T) {
70-
let simpleChanges = this[PRIVATE_PREFIX];
71-
if (!simpleChanges) {
72-
simpleChanges = {};
73-
// Place where we will store SimpleChanges if there is a change
74-
Object.defineProperty(this, PRIVATE_PREFIX, {value: simpleChanges, writable: true});
75-
}
76-
77-
const isFirstChange = !this.hasOwnProperty(privateMinKey);
78-
const currentChange = simpleChanges[declaredKey];
79-
80-
if (currentChange) {
81-
currentChange.currentValue = value;
82-
} else {
83-
simpleChanges[declaredKey] =
84-
new SimpleChange(this[privateMinKey], value, isFirstChange);
85-
}
86-
87-
if (isFirstChange) {
88-
// Create a place where the actual value will be stored and make it non-enumerable
89-
Object.defineProperty(this, privateMinKey, {value, writable: true});
90-
} else {
91-
this[privateMinKey] = value;
92-
}
93-
94-
if (setter) setter.call(this, value);
95-
},
96-
// Make the property configurable in dev mode to allow overriding in tests
97-
configurable: !!ngDevMode
98-
});
99-
}
100-
}
43+
if (definition.type.prototype.ngOnChanges) {
44+
definition.setInput = ngOnChangesSetInput;
45+
46+
const prevDoCheck = definition.doCheck;
47+
const prevOnInit = definition.onInit;
10148

102-
// If an onInit hook is defined, it will need to wrap the ngOnChanges call
103-
// so the call order is changes-init-check in creation mode. In subsequent
104-
// change detection runs, only the check wrapper will be called.
105-
if (definition.onInit != null) {
106-
definition.onInit = onChangesWrapper(definition.onInit);
49+
definition.onInit = wrapOnChanges(prevOnInit);
50+
definition.doCheck = wrapOnChanges(prevDoCheck);
10751
}
52+
}
53+
54+
function wrapOnChanges(hook: (() => void) | null) {
55+
return function(this: OnChanges) {
56+
const simpleChangesStore = getSimpleChangesStore(this);
57+
const current = simpleChangesStore && simpleChangesStore.current;
58+
59+
if (current) {
60+
simpleChangesStore !.previous = current;
61+
simpleChangesStore !.current = null;
62+
this.ngOnChanges(current);
63+
}
64+
65+
hook && hook.call(this);
66+
};
67+
}
10868

109-
definition.doCheck = onChangesWrapper(definition.doCheck);
69+
function ngOnChangesSetInput<T>(
70+
this: DirectiveDef<T>, instance: T, value: any, publicName: string, privateName: string): void {
71+
const simpleChangesStore = getSimpleChangesStore(instance) ||
72+
setSimpleChangesStore(instance, {previous: EMPTY_OBJ, current: null});
73+
const current = simpleChangesStore.current || (simpleChangesStore.current = {});
74+
const previous = simpleChangesStore.previous;
75+
76+
const declaredName = (this.declaredInputs as{[key: string]: string})[publicName];
77+
const previousChange = previous[declaredName];
78+
current[declaredName] = new SimpleChange(
79+
previousChange && previousChange.currentValue, value, previous === EMPTY_OBJ);
80+
81+
(instance as any)[privateName] = value;
82+
}
83+
84+
const SIMPLE_CHANGES_STORE = '__ngSimpleChanges__';
85+
86+
function getSimpleChangesStore(instance: any): null|NgSimpleChangesStore {
87+
return instance[SIMPLE_CHANGES_STORE] || null;
88+
}
89+
90+
function setSimpleChangesStore(instance: any, store: NgSimpleChangesStore): NgSimpleChangesStore {
91+
return instance[SIMPLE_CHANGES_STORE] = store;
11092
}
11193

11294
// This option ensures that the ngOnChanges lifecycle hook will be inherited
11395
// from superclasses (in InheritDefinitionFeature).
11496
(NgOnChangesFeature as DirectiveDefFeature).ngInherit = true;
11597

116-
function onChangesWrapper(delegateHook: (() => void) | null) {
117-
return function(this: OnChangesExpando) {
118-
const simpleChanges = this[PRIVATE_PREFIX];
119-
if (simpleChanges != null) {
120-
this.ngOnChanges(simpleChanges);
121-
this[PRIVATE_PREFIX] = null;
122-
}
123-
if (delegateHook) delegateHook.apply(this);
124-
};
98+
99+
interface NgSimpleChangesStore {
100+
previous: SimpleChanges;
101+
current: SimpleChanges|null;
125102
}

0 commit comments

Comments
 (0)