Skip to content

Commit 8ebdb43

Browse files
benleshAndrewKushnir
authored andcommitted
fix(ivy): ngOnChanges only runs for binding updates (angular#27965)
PR Close angular#27965
1 parent b0caf02 commit 8ebdb43

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+1496
-1425
lines changed

integration/_payload-limits.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"master": {
44
"uncompressed": {
55
"runtime": 1497,
6-
"main": 187134,
6+
"main": 187437,
77
"polyfills": 59608
88
}
99
}

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/compiler-cli/src/ngtsc/annotations/src/directive.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,6 @@ export function extractDirectiveMetadata(
174174
const providers: Expression|null =
175175
directive.has('providers') ? new WrappedNodeExpr(directive.get('providers') !) : null;
176176

177-
// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
178-
const usesOnChanges = members.some(
179-
member => !member.isStatic && member.kind === ClassMemberKind.Method &&
180-
member.name === 'ngOnChanges');
181-
182177
// Parse exportAs.
183178
let exportAs: string[]|null = null;
184179
if (directive.has('exportAs')) {
@@ -197,9 +192,6 @@ export function extractDirectiveMetadata(
197192
const metadata: R3DirectiveMetadata = {
198193
name: clazz.name !.text,
199194
deps: getConstructorDependencies(clazz, reflector, isCore), host,
200-
lifecycle: {
201-
usesOnChanges,
202-
},
203195
inputs: {...inputsFromMeta, ...inputsFromFields},
204196
outputs: {...outputsFromMeta, ...outputsFromFields}, queries, selector,
205197
type: new WrappedNodeExpr(clazz.name !),

packages/compiler-cli/test/compliance/r3_compiler_compliance_spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2117,7 +2117,6 @@ describe('compiler compliance', () => {
21172117
selectors: [["lifecycle-comp"]],
21182118
factory: function LifecycleComp_Factory(t) { return new (t || LifecycleComp)(); },
21192119
inputs: {nameMin: ["name", "nameMin"]},
2120-
features: [$r3$.ɵNgOnChangesFeature],
21212120
consts: 0,
21222121
vars: 0,
21232122
template: function LifecycleComp_Template(rf, ctx) {},
@@ -2242,7 +2241,6 @@ describe('compiler compliance', () => {
22422241
factory: function ForOfDirective_Factory(t) {
22432242
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
22442243
},
2245-
features: [$r3$.ɵNgOnChangesFeature],
22462244
inputs: {forOf: "forOf"}
22472245
});
22482246
`;
@@ -2318,7 +2316,6 @@ describe('compiler compliance', () => {
23182316
factory: function ForOfDirective_Factory(t) {
23192317
return new (t || ForOfDirective)($r3$.ɵdirectiveInject(ViewContainerRef), $r3$.ɵdirectiveInject(TemplateRef));
23202318
},
2321-
features: [$r3$.ɵNgOnChangesFeature],
23222319
inputs: {forOf: "forOf"}
23232320
});
23242321
`;

packages/compiler/src/compiler_facade_interface.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ export interface R3DirectiveMetadataFacade {
115115
queries: R3QueryMetadataFacade[];
116116
host: {[key: string]: string};
117117
propMetadata: {[key: string]: any[]};
118-
lifecycle: {usesOnChanges: boolean;};
119118
inputs: string[];
120119
outputs: string[];
121120
usesInheritance: boolean;

packages/compiler/src/render3/r3_identifiers.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,6 @@ export class Identifiers {
185185
static registerContentQuery:
186186
o.ExternalReference = {name: 'ɵregisterContentQuery', moduleName: CORE};
187187

188-
static NgOnChangesFeature: o.ExternalReference = {name: 'ɵNgOnChangesFeature', moduleName: CORE};
189-
190188
static InheritDefinitionFeature:
191189
o.ExternalReference = {name: 'ɵInheritDefinitionFeature', moduleName: CORE};
192190

packages/compiler/src/render3/view/api.ts

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -74,17 +74,6 @@ export interface R3DirectiveMetadata {
7474
properties: {[key: string]: string};
7575
};
7676

77-
/**
78-
* Information about usage of specific lifecycle events which require special treatment in the
79-
* code generator.
80-
*/
81-
lifecycle: {
82-
/**
83-
* Whether the directive uses NgOnChanges.
84-
*/
85-
usesOnChanges: boolean;
86-
};
87-
8877
/**
8978
* A mapping of input field names to the property names.
9079
*/

packages/compiler/src/render3/view/compiler.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,6 @@ function baseDirectiveFields(
128128
*/
129129
function addFeatures(
130130
definitionMap: DefinitionMap, meta: R3DirectiveMetadata | R3ComponentMetadata) {
131-
// e.g. `features: [NgOnChangesFeature]`
132131
const features: o.Expression[] = [];
133132

134133
const providers = meta.providers;
@@ -144,9 +143,7 @@ function addFeatures(
144143
if (meta.usesInheritance) {
145144
features.push(o.importExpr(R3.InheritDefinitionFeature));
146145
}
147-
if (meta.lifecycle.usesOnChanges) {
148-
features.push(o.importExpr(R3.NgOnChangesFeature));
149-
}
146+
150147
if (features.length) {
151148
definitionMap.set('features', o.literalArr(features));
152149
}
@@ -427,10 +424,6 @@ function directiveMetadataFromGlobalMetadata(
427424
selector: directive.selector,
428425
deps: dependenciesFromGlobalMetadata(directive.type, outputCtx, reflector),
429426
queries: queriesFromGlobalMetadata(directive.queries, outputCtx),
430-
lifecycle: {
431-
usesOnChanges:
432-
directive.type.lifecycleHooks.some(lifecycle => lifecycle == LifecycleHooks.OnChanges),
433-
},
434427
host: {
435428
attributes: directive.hostAttributes,
436429
listeners: summary.hostListeners,

packages/core/src/change_detection/change_detection.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,7 @@ import {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
1111
import {IterableDifferFactory, IterableDiffers} from './differs/iterable_differs';
1212
import {KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
1313

14-
export {SimpleChanges} from '../metadata/lifecycle_hooks';
15-
export {SimpleChange, WrappedValue, devModeEqual} from './change_detection_util';
14+
export {WrappedValue, devModeEqual} from './change_detection_util';
1615
export {ChangeDetectorRef} from './change_detector_ref';
1716
export {ChangeDetectionStrategy, ChangeDetectorStatus, isDefaultChangeDetectionStrategy} from './constants';
1817
export {DefaultIterableDifferFactory} from './differs/default_iterable_differ';
@@ -21,6 +20,7 @@ export {DefaultKeyValueDifferFactory} from './differs/default_keyvalue_differ';
2120
export {CollectionChangeRecord, IterableChangeRecord, IterableChanges, IterableDiffer, IterableDifferFactory, IterableDiffers, NgIterable, TrackByFunction} from './differs/iterable_differs';
2221
export {KeyValueChangeRecord, KeyValueChanges, KeyValueDiffer, KeyValueDifferFactory, KeyValueDiffers} from './differs/keyvalue_differs';
2322
export {PipeTransform} from './pipe_transform';
23+
export {SimpleChange, SimpleChanges} from './simple_change';
2424

2525

2626

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) ||
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
/**
10+
* Represents a basic change from a previous to a new value for a single
11+
* property on a directive instance. Passed as a value in a
12+
* {@link SimpleChanges} object to the `ngOnChanges` hook.
13+
*
14+
* @see `OnChanges`
15+
*
16+
* @publicApi
17+
*/
18+
export class SimpleChange {
19+
constructor(public previousValue: any, public currentValue: any, public firstChange: boolean) {}
20+
/**
21+
* Check whether the new value is the first value assigned.
22+
*/
23+
isFirstChange(): boolean { return this.firstChange; }
24+
}
25+
26+
/**
27+
* A hashtable of changes represented by {@link SimpleChange} objects stored
28+
* at the declared property name they belong to on a Directive or Component. This is
29+
* the type passed to the `ngOnChanges` hook.
30+
*
31+
* @see `OnChanges`
32+
*
33+
* @publicApi
34+
*/
35+
export interface SimpleChanges { [propName: string]: SimpleChange; }

packages/core/src/core_render3_private_export.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ export {
2828
templateRefExtractor as ɵtemplateRefExtractor,
2929
ProvidersFeature as ɵProvidersFeature,
3030
InheritDefinitionFeature as ɵInheritDefinitionFeature,
31-
NgOnChangesFeature as ɵNgOnChangesFeature,
3231
LifecycleHooksFeature as ɵLifecycleHooksFeature,
3332
NgModuleType as ɵNgModuleType,
3433
NgModuleRef as ɵRender3NgModuleRef,

packages/core/src/metadata/directives.ts

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,12 @@
99
import {ChangeDetectionStrategy} from '../change_detection/constants';
1010
import {Provider} from '../di';
1111
import {Type} from '../interface/type';
12-
import {NG_BASE_DEF} from '../render3/fields';
12+
import {NG_BASE_DEF, NG_COMPONENT_DEF, NG_DIRECTIVE_DEF} from '../render3/fields';
1313
import {compileComponent as render3CompileComponent, compileDirective as render3CompileDirective} from '../render3/jit/directive';
1414
import {compilePipe as render3CompilePipe} from '../render3/jit/pipe';
1515
import {TypeDecorator, makeDecorator, makePropDecorator} from '../util/decorators';
1616
import {noop} from '../util/noop';
1717
import {fillProperties} from '../util/property';
18-
1918
import {ViewEncapsulation} from './view';
2019

2120

@@ -715,29 +714,54 @@ const initializeBaseDef = (target: any): void => {
715714
};
716715

717716
/**
718-
* Does the work of creating the `ngBaseDef` property for the @Input and @Output decorators.
719-
* @param key "inputs" or "outputs"
717+
* Returns a function that will update the static definition on a class to have the
718+
* appropriate input or output mapping.
719+
*
720+
* Will also add an {@link ngBaseDef} property to a directive if no `ngDirectiveDef`
721+
* or `ngComponentDef` is present. This is done because a class may have {@link InputDecorator}s and
722+
* {@link OutputDecorator}s without having a {@link ComponentDecorator} or {@link DirectiveDecorator},
723+
* and those inputs and outputs should still be inheritable, we need to add an
724+
* `ngBaseDef` property if there are no existing `ngComponentDef` or `ngDirectiveDef`
725+
* properties, so that we can track the inputs and outputs for inheritance purposes.
726+
*
727+
* @param getPropertyToUpdate A function that maps to either the `inputs` property or the
728+
* `outputs` property of a definition.
729+
* @returns A function that, the called, will add a `ngBaseDef` if no other definition is present,
730+
* then update the `inputs` or `outputs` on it, depending on what was selected by `getPropertyToUpdate`
731+
*
732+
*
733+
* @see InputDecorator
734+
* @see OutputDecorator
735+
* @see InheritenceFeature
720736
*/
721-
const updateBaseDefFromIOProp = (getProp: (baseDef: {inputs?: any, outputs?: any}) => any) =>
722-
(target: any, name: string, ...args: any[]) => {
723-
const constructor = target.constructor;
724-
725-
if (!constructor.hasOwnProperty(NG_BASE_DEF)) {
726-
initializeBaseDef(target);
727-
}
728-
729-
const baseDef = constructor.ngBaseDef;
730-
const defProp = getProp(baseDef);
737+
function getOrCreateDefinitionAndUpdateMappingFor(
738+
getPropertyToUpdate: (baseDef: {inputs?: any, outputs?: any}) => any) {
739+
return function updateIOProp(target: any, name: string, ...args: any[]) {
740+
const constructor = target.constructor;
741+
742+
let def: any =
743+
constructor[NG_COMPONENT_DEF] || constructor[NG_DIRECTIVE_DEF] || constructor[NG_BASE_DEF];
744+
745+
if (!def) {
746+
initializeBaseDef(target);
747+
def = constructor[NG_BASE_DEF];
748+
}
749+
750+
const defProp = getPropertyToUpdate(def);
751+
// Use of `in` because we *do* want to check the prototype chain here.
752+
if (!(name in defProp)) {
731753
defProp[name] = args[0];
732-
};
754+
}
755+
};
756+
}
733757

734758
/**
735759
* @Annotation
736760
* @publicApi
737761
*/
738762
export const Input: InputDecorator = makePropDecorator(
739763
'Input', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
740-
updateBaseDefFromIOProp(baseDef => baseDef.inputs || {}));
764+
getOrCreateDefinitionAndUpdateMappingFor(def => def.inputs || {}));
741765

742766
/**
743767
* Type of the Output decorator / constructor function.
@@ -777,7 +801,7 @@ export interface Output { bindingPropertyName?: string; }
777801
*/
778802
export const Output: OutputDecorator = makePropDecorator(
779803
'Output', (bindingPropertyName?: string) => ({bindingPropertyName}), undefined,
780-
updateBaseDefFromIOProp(baseDef => baseDef.outputs || {}));
804+
getOrCreateDefinitionAndUpdateMappingFor(def => def.outputs || {}));
781805

782806

783807

packages/core/src/metadata/lifecycle_hooks.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,8 @@
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} from '../change_detection/simple_change';
89

9-
import {SimpleChange} from '../change_detection/change_detection_util';
10-
11-
12-
/**
13-
* Defines an object that associates properties with
14-
* instances of `SimpleChange`.
15-
*
16-
* @see `OnChanges`
17-
*
18-
* @publicApi
19-
*/
20-
export interface SimpleChanges { [propName: string]: SimpleChange; }
2110

2211
/**
2312
* @description

packages/core/src/render3/component.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +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 {queueInitHooks, queueLifecycleHooks} from './hooks';
20+
import {registerPostOrderHooks, registerPreOrderHooks} from './hooks';
2121
import {CLEAN_PROMISE, createLView, createNodeAtIndex, createTNode, createTView, getOrCreateTView, initNodeFlags, instantiateRootComponent, locateHostElement, queueComponentIndexForCheck, refreshDescendantViews} from './instructions';
2222
import {ComponentDef, ComponentType, RenderFlags} from './interfaces/definition';
2323
import {TElementNode, TNode, TNodeFlags, TNodeType} from './interfaces/node';
@@ -237,10 +237,11 @@ export function LifecycleHooksFeature(component: any, def: ComponentDef<any>): v
237237
const rootTView = readPatchedLView(component) ![TVIEW];
238238
const dirIndex = rootTView.data.length - 1;
239239

240-
queueInitHooks(dirIndex, def.onInit, def.doCheck, rootTView);
240+
registerPreOrderHooks(dirIndex, def, rootTView);
241241
// TODO(misko): replace `as TNode` with createTNode call. (needs refactoring to lose dep on
242242
// LNode).
243-
queueLifecycleHooks(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/context_discovery.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {LContext, MONKEY_PATCH_KEY_NAME} from './interfaces/context';
1212
import {TNode, TNodeFlags} from './interfaces/node';
1313
import {RElement} from './interfaces/renderer';
1414
import {CONTEXT, HEADER_OFFSET, HOST, LView, TVIEW} from './interfaces/view';
15+
import {unwrapOnChangesDirectiveWrapper} from './onchanges_util';
1516
import {getComponentViewByIndex, getNativeByTNode, readElementValue, readPatchedData} from './util';
1617

1718

@@ -257,7 +258,7 @@ function findViaDirective(lView: LView, directiveInstance: {}): number {
257258
const directiveIndexStart = tNode.directiveStart;
258259
const directiveIndexEnd = tNode.directiveEnd;
259260
for (let i = directiveIndexStart; i < directiveIndexEnd; i++) {
260-
if (lView[i] === directiveInstance) {
261+
if (unwrapOnChangesDirectiveWrapper(lView[i]) === directiveInstance) {
261262
return tNode.index;
262263
}
263264
}

0 commit comments

Comments
 (0)