fix(ivy): determine value of SimpleChange.firstChange per property (#24152)
PR Close #24152
This commit is contained in:
parent
7e3f8f77a9
commit
a5c47d0045
|
@ -253,32 +253,33 @@ export function NgOnChangesFeature(inputPropertyNames?: {[key: string]: string})
|
||||||
return function(definition: DirectiveDef<any>): void {
|
return function(definition: DirectiveDef<any>): void {
|
||||||
const inputs = definition.inputs;
|
const inputs = definition.inputs;
|
||||||
const proto = definition.type.prototype;
|
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) {
|
for (let pubKey in inputs) {
|
||||||
const minKey = inputs[pubKey];
|
const minKey = inputs[pubKey];
|
||||||
const propertyName = inputPropertyNames && inputPropertyNames[minKey] || pubKey;
|
const propertyName = inputPropertyNames && inputPropertyNames[minKey] || pubKey;
|
||||||
const privateMinKey = PRIVATE_PREFIX + minKey;
|
const privateMinKey = PRIVATE_PREFIX + minKey;
|
||||||
// Create a place where the actual value will be stored and make it non-enumerable
|
const originalProperty = Object.getOwnPropertyDescriptor(proto, minKey);
|
||||||
Object.defineProperty(proto, privateMinKey, {value: undefined, writable: true});
|
const getter = originalProperty && originalProperty.get;
|
||||||
|
const setter = originalProperty && originalProperty.set;
|
||||||
const existingDesc = Object.getOwnPropertyDescriptor(proto, minKey);
|
|
||||||
|
|
||||||
// create a getter and setter for property
|
// create a getter and setter for property
|
||||||
Object.defineProperty(proto, minKey, {
|
Object.defineProperty(proto, minKey, {
|
||||||
get: function(this: OnChangesExpando) {
|
get: getter ||
|
||||||
return (existingDesc && existingDesc.get) ? existingDesc.get.call(this) :
|
(setter ? undefined : function(this: OnChangesExpando) { return this[privateMinKey]; }),
|
||||||
this[privateMinKey];
|
|
||||||
},
|
|
||||||
set: function(this: OnChangesExpando, value: any) {
|
set: function(this: OnChangesExpando, value: any) {
|
||||||
let simpleChanges = this[PRIVATE_PREFIX];
|
let simpleChanges = this[PRIVATE_PREFIX];
|
||||||
let isFirstChange = simpleChanges === undefined;
|
if (!simpleChanges) {
|
||||||
if (simpleChanges == null) {
|
// Place where we will store SimpleChanges if there is a change
|
||||||
simpleChanges = this[PRIVATE_PREFIX] = {};
|
Object.defineProperty(
|
||||||
|
this, PRIVATE_PREFIX, {value: simpleChanges = {}, writable: true});
|
||||||
}
|
}
|
||||||
|
const isFirstChange = !this.hasOwnProperty(privateMinKey);
|
||||||
simpleChanges[propertyName] = new SimpleChange(this[privateMinKey], value, isFirstChange);
|
simpleChanges[propertyName] = new SimpleChange(this[privateMinKey], value, isFirstChange);
|
||||||
(existingDesc && existingDesc.set) ? existingDesc.set.call(this, value) :
|
if (isFirstChange) {
|
||||||
this[privateMinKey] = value;
|
// Create a place where the actual value will be stored and make it non-enumerable
|
||||||
|
Object.defineProperty(this, privateMinKey, {value, writable: true});
|
||||||
|
} else {
|
||||||
|
this[privateMinKey] = value;
|
||||||
|
}
|
||||||
|
setter && setter.call(this, value);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
* found in the LICENSE file at https://angular.io/license
|
* found in the LICENSE file at https://angular.io/license
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {DoCheck, OnChanges, SimpleChanges} from '../../src/core';
|
import {DoCheck, OnChanges, SimpleChange, SimpleChanges} from '../../src/core';
|
||||||
import {DirectiveDef, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
|
import {DirectiveDef, NgOnChangesFeature, defineDirective} from '../../src/render3/index';
|
||||||
|
|
||||||
describe('define', () => {
|
describe('define', () => {
|
||||||
|
@ -14,7 +14,7 @@ describe('define', () => {
|
||||||
describe('NgOnChangesFeature', () => {
|
describe('NgOnChangesFeature', () => {
|
||||||
it('should patch class', () => {
|
it('should patch class', () => {
|
||||||
class MyDirective implements OnChanges, DoCheck {
|
class MyDirective implements OnChanges, DoCheck {
|
||||||
public log: string[] = [];
|
public log: Array<string|SimpleChange> = [];
|
||||||
public valA: string = 'initValue';
|
public valA: string = 'initValue';
|
||||||
public set valB(value: string) { this.log.push(value); }
|
public set valB(value: string) { this.log.push(value); }
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ describe('define', () => {
|
||||||
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
ngDoCheck(): void { this.log.push('ngDoCheck'); }
|
||||||
ngOnChanges(changes: SimpleChanges): void {
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
this.log.push('ngOnChanges');
|
this.log.push('ngOnChanges');
|
||||||
this.log.push('valA', changes['valA'].previousValue, changes['valA'].currentValue);
|
this.log.push('valA', changes['valA']);
|
||||||
this.log.push('valB', changes['valB'].previousValue, changes['valB'].currentValue);
|
this.log.push('valB', changes['valB']);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ngDirectiveDef = defineDirective({
|
static ngDirectiveDef = defineDirective({
|
||||||
|
@ -45,9 +45,74 @@ describe('define', () => {
|
||||||
expect(myDir.valB).toEqual('works');
|
expect(myDir.valB).toEqual('works');
|
||||||
myDir.log.length = 0;
|
myDir.log.length = 0;
|
||||||
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
|
||||||
expect(myDir.log).toEqual([
|
const changeA = new SimpleChange('initValue', 'first', false);
|
||||||
'ngOnChanges', 'valA', 'initValue', 'first', 'valB', undefined, 'second', 'ngDoCheck'
|
const changeB = new SimpleChange(undefined, 'second', true);
|
||||||
]);
|
expect(myDir.log).toEqual(['ngOnChanges', 'valA', changeA, 'valB', changeB, 'ngDoCheck']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('correctly computes firstChange', () => {
|
||||||
|
class MyDirective implements OnChanges {
|
||||||
|
public log: Array<string|SimpleChange> = [];
|
||||||
|
public valA: string = 'initValue';
|
||||||
|
public valB: string;
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('valA', changes['valA']);
|
||||||
|
this.log.push('valB', changes['valB']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: MyDirective,
|
||||||
|
selectors: [['', 'myDir', '']],
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature()],
|
||||||
|
inputs: {valA: 'valA', valB: 'valB'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir =
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory() as MyDirective;
|
||||||
|
myDir.valA = 'first';
|
||||||
|
myDir.valB = 'second';
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA1 = new SimpleChange('initValue', 'first', false);
|
||||||
|
const changeB1 = new SimpleChange(undefined, 'second', true);
|
||||||
|
expect(myDir.log).toEqual(['valA', changeA1, 'valB', changeB1]);
|
||||||
|
|
||||||
|
myDir.log.length = 0;
|
||||||
|
myDir.valA = 'third';
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeA2 = new SimpleChange('first', 'third', false);
|
||||||
|
expect(myDir.log).toEqual(['valA', changeA2, 'valB', undefined]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not create a getter when only a setter is originally defined', () => {
|
||||||
|
class MyDirective implements OnChanges {
|
||||||
|
public log: Array<string|SimpleChange> = [];
|
||||||
|
|
||||||
|
public set onlySetter(value: string) { this.log.push(value); }
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges): void {
|
||||||
|
this.log.push('ngOnChanges');
|
||||||
|
this.log.push('onlySetter', changes['onlySetter']);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ngDirectiveDef = defineDirective({
|
||||||
|
type: MyDirective,
|
||||||
|
selectors: [['', 'myDir', '']],
|
||||||
|
factory: () => new MyDirective(),
|
||||||
|
features: [NgOnChangesFeature()],
|
||||||
|
inputs: {onlySetter: 'onlySetter'}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const myDir =
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).factory() as MyDirective;
|
||||||
|
myDir.onlySetter = 'someValue';
|
||||||
|
expect(myDir.onlySetter).toBeUndefined();
|
||||||
|
(MyDirective.ngDirectiveDef as DirectiveDef<MyDirective>).doCheck !.call(myDir);
|
||||||
|
const changeSetter = new SimpleChange(undefined, 'someValue', true);
|
||||||
|
expect(myDir.log).toEqual(['someValue', 'ngOnChanges', 'onlySetter', changeSetter]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue