From 7151eae36de08b6ab7318d90f679612f5b89e005 Mon Sep 17 00:00:00 2001 From: Balint Mero Date: Wed, 17 Jul 2019 13:39:54 +0200 Subject: [PATCH] fix(elements): handle falsy initial value (#31604) Fixes angular/angular#30834 PR Close #31604 --- .../src/component-factory-strategy.ts | 11 ++--- .../test/component-factory-strategy_spec.ts | 46 +++++++++++++++++-- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/packages/elements/src/component-factory-strategy.ts b/packages/elements/src/component-factory-strategy.ts index 241642193b..8f27a721c9 100644 --- a/packages/elements/src/component-factory-strategy.ts +++ b/packages/elements/src/component-factory-strategy.ts @@ -125,12 +125,12 @@ export class ComponentNgElementStrategy implements NgElementStrategy { * cached and set when the component is created. */ setInputValue(property: string, value: any): void { - if (strictEquals(value, this.getInputValue(property))) { + if (!this.componentRef) { + this.initialInputValues.set(property, value); return; } - if (!this.componentRef) { - this.initialInputValues.set(property, value); + if (strictEquals(value, this.getInputValue(property))) { return; } @@ -164,9 +164,8 @@ export class ComponentNgElementStrategy implements NgElementStrategy { /** Set any stored initial inputs on the component's properties. */ protected initializeInputs(): void { this.componentFactory.inputs.forEach(({propName}) => { - const initialValue = this.initialInputValues.get(propName); - if (initialValue) { - this.setInputValue(propName, initialValue); + if (this.initialInputValues.has(propName)) { + this.setInputValue(propName, this.initialInputValues.get(propName)); } else { // Keep track of inputs that were not initialized in case we need to know this for // calling ngOnChanges with SimpleChanges diff --git a/packages/elements/test/component-factory-strategy_spec.ts b/packages/elements/test/component-factory-strategy_spec.ts index dbf702b27f..54b8bd8f8a 100644 --- a/packages/elements/test/component-factory-strategy_spec.ts +++ b/packages/elements/test/component-factory-strategy_spec.ts @@ -45,6 +45,11 @@ describe('ComponentFactoryNgElementStrategy', () => { beforeEach(() => { // Set up an initial value to make sure it is passed to the component strategy.setInputValue('fooFoo', 'fooFoo-1'); + strategy.setInputValue('falsyUndefined', undefined); + strategy.setInputValue('falsyNull', null); + strategy.setInputValue('falsyEmpty', ''); + strategy.setInputValue('falsyFalse', false); + strategy.setInputValue('falsyZero', 0); strategy.connect(document.createElement('div')); }); @@ -73,11 +78,39 @@ describe('ComponentFactoryNgElementStrategy', () => { expect(componentRef.instance.fooFoo).toBe('fooFoo-1'); }); - it('should call ngOnChanges with the change', () => { - expectSimpleChanges( - componentRef.instance.simpleChanges[0], - {fooFoo: new SimpleChange(undefined, 'fooFoo-1', false)}); + it('should initialize the component with falsy initial values', () => { + expect(strategy.getInputValue('falsyUndefined')).toEqual(undefined); + expect(componentRef.instance.falsyUndefined).toEqual(undefined); + expect(strategy.getInputValue('falsyNull')).toEqual(null); + expect(componentRef.instance.falsyNull).toEqual(null); + expect(strategy.getInputValue('falsyEmpty')).toEqual(''); + expect(componentRef.instance.falsyEmpty).toEqual(''); + expect(strategy.getInputValue('falsyFalse')).toEqual(false); + expect(componentRef.instance.falsyFalse).toEqual(false); + expect(strategy.getInputValue('falsyZero')).toEqual(0); + expect(componentRef.instance.falsyZero).toEqual(0); }); + + it('should call ngOnChanges with the change', () => { + expectSimpleChanges(componentRef.instance.simpleChanges[0], { + fooFoo: new SimpleChange(undefined, 'fooFoo-1', false), + falsyNull: new SimpleChange(undefined, null, false), + falsyEmpty: new SimpleChange(undefined, '', false), + falsyFalse: new SimpleChange(undefined, false, false), + falsyZero: new SimpleChange(undefined, 0, false), + }); + }); + + it('should call ngOnChanges with proper firstChange value', fakeAsync(() => { + strategy.setInputValue('falsyUndefined', 'notanymore'); + strategy.setInputValue('barBar', 'barBar-1'); + tick(16); // scheduler waits 16ms if RAF is unavailable + (strategy as any).detectChanges(); + expectSimpleChanges(componentRef.instance.simpleChanges[1], { + falsyUndefined: new SimpleChange(undefined, 'notanymore', false), + barBar: new SimpleChange(undefined, 'barBar-1', true), + }); + })); }); it('should not call ngOnChanges if not present on the component', () => { @@ -234,6 +267,11 @@ export class FakeComponentFactory extends ComponentFactory { return [ {propName: 'fooFoo', templateName: 'fooFoo'}, {propName: 'barBar', templateName: 'my-bar-bar'}, + {propName: 'falsyUndefined', templateName: 'falsyUndefined'}, + {propName: 'falsyNull', templateName: 'falsyNull'}, + {propName: 'falsyEmpty', templateName: 'falsyEmpty'}, + {propName: 'falsyFalse', templateName: 'falsyFalse'}, + {propName: 'falsyZero', templateName: 'falsyZero'}, ]; }