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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user