From 4ec045e12b7614b901d4ef643988b86f09dfeee5 Mon Sep 17 00:00:00 2001 From: Michael Jerred Date: Thu, 11 Feb 2021 12:01:32 +0000 Subject: [PATCH] feat(forms): add `emitEvent` option for AbstractControl-based class methods (#31031) This commit adds the `emitEvent` option to the following FormArray and FormGroup methods: * FormGroup.addControl * FormGroup.removeControl * FormGroup.setControl * FormArray.push * FormArray.insert * FormArray.removeAt * FormArray.setControl * FormArray.clear This option can be used to prevent an event from being emitted when adding or removing controls. BREAKING CHANGE: The `emitEvent` option was added to the following `FormArray` and `FormGroup` methods: * FormGroup.addControl * FormGroup.removeControl * FormGroup.setControl * FormArray.push * FormArray.insert * FormArray.removeAt * FormArray.setControl * FormArray.clear If your app has custom classes that extend `FormArray` or `FormGroup` classes and override the above-mentioned methods, you may need to update your implementation to take the new options into account and make sure that overrides are compatible from a types perspective. Closes #29662. PR Close #31031 --- goldens/public-api/forms/forms.d.ts | 32 +++-- packages/forms/src/model.ts | 75 ++++++++--- packages/forms/test/form_array_spec.ts | 178 +++++++++++++++++-------- packages/forms/test/form_group_spec.ts | 139 +++++++++++++------ 4 files changed, 305 insertions(+), 119 deletions(-) diff --git a/goldens/public-api/forms/forms.d.ts b/goldens/public-api/forms/forms.d.ts index 8116d8cec0..aed8763bb8 100644 --- a/goldens/public-api/forms/forms.d.ts +++ b/goldens/public-api/forms/forms.d.ts @@ -172,20 +172,30 @@ export declare class FormArray extends AbstractControl { get length(): number; constructor(controls: AbstractControl[], validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); at(index: number): AbstractControl; - clear(): void; + clear(options?: { + emitEvent?: boolean; + }): void; getRawValue(): any[]; - insert(index: number, control: AbstractControl): void; + insert(index: number, control: AbstractControl, options?: { + emitEvent?: boolean; + }): void; patchValue(value: any[], options?: { onlySelf?: boolean; emitEvent?: boolean; }): void; - push(control: AbstractControl): void; - removeAt(index: number): void; + push(control: AbstractControl, options?: { + emitEvent?: boolean; + }): void; + removeAt(index: number, options?: { + emitEvent?: boolean; + }): void; reset(value?: any, options?: { onlySelf?: boolean; emitEvent?: boolean; }): void; - setControl(index: number, control: AbstractControl): void; + setControl(index: number, control: AbstractControl, options?: { + emitEvent?: boolean; + }): void; setValue(value: any[], options?: { onlySelf?: boolean; emitEvent?: boolean; @@ -272,7 +282,9 @@ export declare class FormGroup extends AbstractControl { constructor(controls: { [key: string]: AbstractControl; }, validatorOrOpts?: ValidatorFn | ValidatorFn[] | AbstractControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null); - addControl(name: string, control: AbstractControl): void; + addControl(name: string, control: AbstractControl, options?: { + emitEvent?: boolean; + }): void; contains(controlName: string): boolean; getRawValue(): any; patchValue(value: { @@ -282,12 +294,16 @@ export declare class FormGroup extends AbstractControl { emitEvent?: boolean; }): void; registerControl(name: string, control: AbstractControl): AbstractControl; - removeControl(name: string): void; + removeControl(name: string, options?: { + emitEvent?: boolean; + }): void; reset(value?: any, options?: { onlySelf?: boolean; emitEvent?: boolean; }): void; - setControl(name: string, control: AbstractControl): void; + setControl(name: string, control: AbstractControl, options?: { + emitEvent?: boolean; + }): void; setValue(value: { [key: string]: any; }, options?: { diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index c21fe81f08..f7b75fda46 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -1450,22 +1450,34 @@ export class FormGroup extends AbstractControl { * * @param name The control name to add to the collection * @param control Provides the control for the given name + * @param options Specifies whether this FormGroup instance should emit events after a new + * control is added. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * added. When false, no events are emitted. */ - addControl(name: string, control: AbstractControl): void { + addControl(name: string, control: AbstractControl, options: {emitEvent?: boolean} = {}): void { this.registerControl(name, control); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); this._onCollectionChange(); } /** * Remove a control from this group. * + * This method also updates the value and validity of the control. + * * @param name The control name to remove from the collection + * @param options Specifies whether this FormGroup instance should emit events after a + * control is removed. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * removed. When false, no events are emitted. */ - removeControl(name: string): void { + removeControl(name: string, options: {emitEvent?: boolean} = {}): void { if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {}); delete (this.controls[name]); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); this._onCollectionChange(); } @@ -1474,12 +1486,17 @@ export class FormGroup extends AbstractControl { * * @param name The control name to replace in the collection * @param control Provides the control for the given name + * @param options Specifies whether this FormGroup instance should emit events after an + * existing control is replaced. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * replaced with a new one. When false, no events are emitted. */ - setControl(name: string, control: AbstractControl): void { + setControl(name: string, control: AbstractControl, options: {emitEvent?: boolean} = {}): void { if (this.controls[name]) this.controls[name]._registerOnCollectionChange(() => {}); delete (this.controls[name]); if (control) this.registerControl(name, control); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); this._onCollectionChange(); } @@ -1876,11 +1893,16 @@ export class FormArray extends AbstractControl { * Insert a new `AbstractControl` at the end of the array. * * @param control Form control to be inserted + * @param options Specifies whether this FormArray instance should emit events after a new + * control is added. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * inserted. When false, no events are emitted. */ - push(control: AbstractControl): void { + push(control: AbstractControl, options: {emitEvent?: boolean} = {}): void { this.controls.push(control); this._registerControl(control); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); this._onCollectionChange(); } @@ -1889,23 +1911,33 @@ export class FormArray extends AbstractControl { * * @param index Index in the array to insert the control * @param control Form control to be inserted + * @param options Specifies whether this FormArray instance should emit events after a new + * control is inserted. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * inserted. When false, no events are emitted. */ - insert(index: number, control: AbstractControl): void { + insert(index: number, control: AbstractControl, options: {emitEvent?: boolean} = {}): void { this.controls.splice(index, 0, control); this._registerControl(control); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); } /** * Remove the control at the given `index` in the array. * * @param index Index in the array to remove the control + * @param options Specifies whether this FormArray instance should emit events after a + * control is removed. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * removed. When false, no events are emitted. */ - removeAt(index: number): void { + removeAt(index: number, options: {emitEvent?: boolean} = {}): void { if (this.controls[index]) this.controls[index]._registerOnCollectionChange(() => {}); this.controls.splice(index, 1); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); } /** @@ -1913,8 +1945,13 @@ export class FormArray extends AbstractControl { * * @param index Index in the array to replace the control * @param control The `AbstractControl` control to replace the existing control + * @param options Specifies whether this FormArray instance should emit events after an + * existing control is replaced with a new one. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when the control is + * replaced with a new one. When false, no events are emitted. */ - setControl(index: number, control: AbstractControl): void { + setControl(index: number, control: AbstractControl, options: {emitEvent?: boolean} = {}): void { if (this.controls[index]) this.controls[index]._registerOnCollectionChange(() => {}); this.controls.splice(index, 1); @@ -1923,7 +1960,7 @@ export class FormArray extends AbstractControl { this._registerControl(control); } - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); this._onCollectionChange(); } @@ -2095,6 +2132,12 @@ export class FormArray extends AbstractControl { /** * Remove all controls in the `FormArray`. * + * @param options Specifies whether this FormArray instance should emit events after all + * controls are removed. + * * `emitEvent`: When true or not supplied (the default), both the `statusChanges` and + * `valueChanges` observables emit events with the latest status and value when all controls + * in this FormArray instance are removed. When false, no events are emitted. + * * @usageNotes * ### Remove all elements from a FormArray * @@ -2122,11 +2165,11 @@ export class FormArray extends AbstractControl { * } * ``` */ - clear(): void { + clear(options: {emitEvent?: boolean} = {}): void { if (this.controls.length < 1) return; this._forEachChild((control: AbstractControl) => control._registerOnCollectionChange(() => {})); this.controls.splice(0); - this.updateValueAndValidity(); + this.updateValueAndValidity({emitEvent: options.emitEvent}); } /** @internal */ diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index d1f2737b21..c810230d17 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -18,12 +18,14 @@ describe('FormArray', () => { describe('adding/removing', () => { let a: FormArray; let c1: FormControl, c2: FormControl, c3: FormControl; + let logger: string[]; beforeEach(() => { a = new FormArray([]); c1 = new FormControl(1); c2 = new FormControl(2); c3 = new FormControl(3); + logger = []; }); it('should support pushing', () => { @@ -64,6 +66,100 @@ describe('FormArray', () => { expect(a.controls).toEqual([c1, c2, c3]); }); + + it('should not emit events when calling `FormArray.push` with `emitEvent: false`', () => { + a.valueChanges.subscribe(() => logger.push('value change')); + a.statusChanges.subscribe(() => logger.push('status change')); + + a.push(c1, {emitEvent: false}); + + expect(a.length).toEqual(1); + expect(a.controls).toEqual([c1]); + expect(logger).toEqual([]); + }); + + it('should not emit events when calling `FormArray.removeAt` with `emitEvent: false`', () => { + a.push(c1); + a.push(c2); + a.push(c3); + + a.valueChanges.subscribe(() => logger.push('value change')); + a.statusChanges.subscribe(() => logger.push('status change')); + + a.removeAt(1, {emitEvent: false}); + + expect(a.controls).toEqual([c1, c3]); + expect(logger).toEqual([]); + }); + + it('should not emit events when calling `FormArray.clear` with `emitEvent: false`', () => { + a.push(c1); + a.push(c2); + a.push(c3); + + a.valueChanges.subscribe(() => logger.push('value change')); + a.statusChanges.subscribe(() => logger.push('status change')); + + a.clear({emitEvent: false}); + + expect(a.controls).toEqual([]); + expect(logger).toEqual([]); + }); + + it('should not emit events when calling `FormArray.insert` with `emitEvent: false`', () => { + a.push(c1); + a.push(c3); + + a.valueChanges.subscribe(() => logger.push('value change')); + a.statusChanges.subscribe(() => logger.push('status change')); + + a.insert(1, c2, {emitEvent: false}); + + expect(a.controls).toEqual([c1, c2, c3]); + expect(logger).toEqual([]); + }); + + it('should not emit events when calling `FormArray.setControl` with `emitEvent: false`', () => { + a.push(c1); + a.push(c3); + + a.valueChanges.subscribe(() => logger.push('value change')); + a.statusChanges.subscribe(() => logger.push('status change')); + + a.setControl(1, c2, {emitEvent: false}); + + expect(a.controls).toEqual([c1, c2]); + expect(logger).toEqual([]); + }); + + it('should not emit status change events when `FormArray.push` is called with `emitEvent: false`', + () => { + // Adding validators to make sure there are no status change event submitted when form + // becomes invalid. + const validatorFn = (value: any) => value.controls.length > 0 ? {controls: true} : null; + const asyncValidatorFn = (value: any) => of(validatorFn(value)); + const arr = new FormArray([], validatorFn, asyncValidatorFn); + expect(arr.valid).toBe(true); + + arr.statusChanges.subscribe(() => logger.push('status change')); + + arr.push(c1, {emitEvent: false}); + + expect(arr.valid).toBe(false); + expect(logger).toEqual([]); + }); + + it('should not emit events on the parent when called with `emitEvent: false`', () => { + const form = new FormGroup({child: a}); + + form.valueChanges.subscribe(() => logger.push('form value change')); + a.valueChanges.subscribe(() => logger.push('array value change')); + form.statusChanges.subscribe(() => logger.push('form status change')); + a.statusChanges.subscribe(() => logger.push('array status change')); + + a.push(new FormControl(5), {emitEvent: false}); + expect(logger).toEqual([]); + }); }); describe('value', () => { @@ -235,23 +331,15 @@ describe('FormArray', () => { expect(logger).toEqual(['control1', 'control2', 'array', 'form']); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - a.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c2.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not fire events when called with `emitEvent: false`', () => { + form.valueChanges.subscribe(() => logger.push('form')); + a.valueChanges.subscribe(() => logger.push('array')); + c.valueChanges.subscribe(() => logger.push('control1')); + c2.valueChanges.subscribe(() => logger.push('control2')); - a.setValue(['one', 'two'], {emitEvent: false}); - tick(); - })); + a.setValue(['one', 'two'], {emitEvent: false}); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -386,23 +474,15 @@ describe('FormArray', () => { expect(logger).toEqual([]); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - a.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c2.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not fire events when called with `emitEvent: false`', () => { + form.valueChanges.subscribe(() => logger.push('form')); + a.valueChanges.subscribe(() => logger.push('array')); + c.valueChanges.subscribe(() => logger.push('control1')); + c2.valueChanges.subscribe(() => logger.push('control2')); - a.patchValue(['one', 'two'], {emitEvent: false}); - tick(); - })); + a.patchValue(['one', 'two'], {emitEvent: false}); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -605,26 +685,16 @@ describe('FormArray', () => { expect(logger).toEqual(['control1', 'control2', 'array', 'form']); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - a.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c2.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c3.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not fire events when called with `emitEvent: false`', () => { + form.valueChanges.subscribe(() => logger.push('form')); + a.valueChanges.subscribe(() => logger.push('array')); + c.valueChanges.subscribe(() => logger.push('control1')); + c2.valueChanges.subscribe(() => logger.push('control2')); + c3.valueChanges.subscribe(() => logger.push('control3')); - a.reset([], {emitEvent: false}); - tick(); - })); + a.reset([], {emitEvent: false}); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per reset control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -764,7 +834,7 @@ describe('FormArray', () => { expect(logger).toEqual([]); }); - it('should not emit event when emitEvent = false', () => { + it('should not emit events when called with `emitEvent: false`', () => { c.markAsPending({emitEvent: false}); expect(logger).toEqual([]); }); @@ -1211,7 +1281,7 @@ describe('FormArray', () => { expect(logger).toEqual(['control', 'array', 'form']); }); - it('should not emit value change events when emitEvent = false', () => { + it('should not emit value change events when called with `emitEvent: false`', () => { c.valueChanges.subscribe(() => logger.push('control')); a.valueChanges.subscribe(() => logger.push('array')); form.valueChanges.subscribe(() => logger.push('form')); @@ -1222,7 +1292,7 @@ describe('FormArray', () => { expect(logger).toEqual([]); }); - it('should not emit status change events when emitEvent = false', () => { + it('should not emit status change events when called with `emitEvent: false`', () => { c.statusChanges.subscribe(() => logger.push('control')); a.statusChanges.subscribe(() => logger.push('array')); form.statusChanges.subscribe(() => logger.push('form')); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index f65e88aa9f..6a8578bef1 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -126,6 +126,12 @@ describe('FormGroup', () => { }); describe('adding and removing controls', () => { + let logger: any[]; + + beforeEach(() => { + logger = []; + }); + it('should update value and validity when control is added', () => { const g = new FormGroup({'one': new FormControl('1')}); expect(g.value).toEqual({'one': '1'}); @@ -148,6 +154,54 @@ describe('FormGroup', () => { expect(g.value).toEqual({'one': '1'}); expect(g.valid).toBe(true); }); + + it('should not emit events when `FormGroup.addControl` is called with `emitEvent: false`', + () => { + const g = new FormGroup({'one': new FormControl('1')}); + expect(g.value).toEqual({'one': '1'}); + + g.valueChanges.subscribe(() => logger.push('value change')); + g.statusChanges.subscribe(() => logger.push('status change')); + + g.addControl('two', new FormControl('2'), {emitEvent: false}); + + expect(g.value).toEqual({'one': '1', 'two': '2'}); + expect(logger).toEqual([]); + }); + + it('should not emit events when `FormGroup.removeControl` is called with `emitEvent: false`', + () => { + const g = new FormGroup( + {'one': new FormControl('1'), 'two': new FormControl('2', Validators.minLength(10))}); + expect(g.value).toEqual({'one': '1', 'two': '2'}); + expect(g.valid).toBe(false); + + g.valueChanges.subscribe(() => logger.push('value change')); + g.statusChanges.subscribe(() => logger.push('status change')); + + g.removeControl('two', {emitEvent: false}); + + expect(g.value).toEqual({'one': '1'}); + expect(g.valid).toBe(true); + expect(logger).toEqual([]); + }); + + it('should not emit status change events when `FormGroup.addControl` is called with `emitEvent: false`', + () => { + const validatorFn = (value: any) => + value.controls.invalidCtrl ? {invalidCtrl: true} : null; + const asyncValidatorFn = (value: any) => of(validatorFn(value)); + const g = new FormGroup({}, validatorFn, asyncValidatorFn); + expect(g.valid).toBe(true); + + g.statusChanges.subscribe(() => logger.push('status change')); + + g.addControl('invalidCtrl', new FormControl(''), {emitEvent: false}); + + expect(g.value).toEqual({'invalidCtrl': ''}); + expect(g.valid).toBe(false); + expect(logger).toEqual([]); + }); }); describe('dirty', () => { @@ -283,20 +337,15 @@ describe('FormGroup', () => { expect(logger).toEqual(['control1', 'control2', 'group', 'form']); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - g.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not emit events when `FormGroup.setValue` is called with `emitEvent: false`', + () => { + form.valueChanges.subscribe(() => logger.push('form')); + g.valueChanges.subscribe(() => logger.push('group')); + c.valueChanges.subscribe(() => logger.push('control')); g.setValue({'one': 'one', 'two': 'two'}, {emitEvent: false}); - tick(); - })); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -307,6 +356,16 @@ describe('FormGroup', () => { g.setValue({'one': 'one', 'two': 'two'}); expect(logger).toEqual(['control1', 'control2', 'group', 'form']); }); + + it('should not emit events on the parent when called with `emitEvent: false`', () => { + form.valueChanges.subscribe(() => logger.push('form value change')); + g.valueChanges.subscribe(() => logger.push('group value change')); + form.statusChanges.subscribe(() => logger.push('form status change')); + g.statusChanges.subscribe(() => logger.push('group status change')); + + g.addControl('three', new FormControl(5), {emitEvent: false}); + expect(logger).toEqual([]); + }); }); }); @@ -444,20 +503,15 @@ describe('FormGroup', () => { expect(logger).toEqual([]); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - g.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not emit events when `FormGroup.patchValue` is called with `emitEvent: false`', + () => { + form.valueChanges.subscribe(() => logger.push('form')); + g.valueChanges.subscribe(() => logger.push('group')); + c.valueChanges.subscribe(() => logger.push('control')); g.patchValue({'one': 'one', 'two': 'two'}, {emitEvent: false}); - tick(); - })); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -659,20 +713,14 @@ describe('FormGroup', () => { expect(logger).toEqual(['control1', 'control2', 'group', 'form']); }); - it('should not fire an event when explicitly specified', fakeAsync(() => { - form.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - g.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); - c.valueChanges.subscribe((value) => { - throw 'Should not happen'; - }); + it('should not emit events when `FormGroup.reset` is called with `emitEvent: false`', () => { + form.valueChanges.subscribe(() => logger.push('form')); + g.valueChanges.subscribe(() => logger.push('group')); + c.valueChanges.subscribe(() => logger.push('control')); - g.reset({}, {emitEvent: false}); - tick(); - })); + g.reset({}, {emitEvent: false}); + expect(logger).toEqual([]); + }); it('should emit one statusChange event per reset control', () => { form.statusChanges.subscribe(() => logger.push('form')); @@ -1769,7 +1817,7 @@ describe('FormGroup', () => { expect(logger).toEqual(['control', 'group', 'form']); }); - it('should not emit value change events when emitEvent = false', () => { + it('should not emit value change events when called with `emitEvent: false`', () => { c.valueChanges.subscribe(() => logger.push('control')); g.valueChanges.subscribe(() => logger.push('group')); form.valueChanges.subscribe(() => logger.push('form')); @@ -1780,7 +1828,7 @@ describe('FormGroup', () => { expect(logger).toEqual([]); }); - it('should not emit status change events when emitEvent = false', () => { + it('should not emit status change events when called with `emitEvent: false`', () => { c.statusChanges.subscribe(() => logger.push('control')); g.statusChanges.subscribe(() => logger.push('group')); form.statusChanges.subscribe(() => logger.push('form')); @@ -1818,7 +1866,7 @@ describe('FormGroup', () => { expect(logger).toEqual(['one', 'two', 'nested', 'three', 'form']); }); - it('should not emit events when turned off', () => { + it('should not emit events when called with `emitEvent: false`', () => { (form as any)._updateTreeValidity({emitEvent: false}); expect(logger).toEqual([]); }); @@ -1864,6 +1912,15 @@ describe('FormGroup', () => { g.setControl('one', c2); expect(logger).toEqual(['change!']); }); + + it('should not emit event called when `FormGroup.setControl` with `emitEvent: false`', () => { + const logger: string[] = []; + const c2 = new FormControl('new!'); + g.valueChanges.subscribe(() => logger.push('value change')); + g.statusChanges.subscribe(() => logger.push('status change')); + g.setControl('one', c2, {emitEvent: false}); + expect(logger).toEqual([]); + }); }); describe('emit `statusChanges` and `valueChanges` with/without async/sync validators', () => { @@ -2292,7 +2349,7 @@ describe('FormGroup', () => { expect(logger).toEqual([]); }); - it('should not emit event when emitEvent = false', () => { + it('should not emit event when called with `emitEvent: false`', () => { c.markAsPending({emitEvent: false}); expect(logger).toEqual([]); });