From e86b64b620c8fffbc956c4f0d61fecc6a2566caa Mon Sep 17 00:00:00 2001 From: Fabian Wiles Date: Mon, 6 Nov 2017 21:59:09 +1300 Subject: [PATCH] feat(forms): allow markAsPending to emit events (#20212) closes #17958 BREAKING CHANGE: - `AbstractControl#statusChanges` now emits an event of `'PENDING'` when you call `AbstractControl#markAsPending` - Previously it did not emit an event when you called `markAsPending` - To migrate you would need to ensure that if you are filtering or checking events from `statusChanges` that you account for the new event when calling `markAsPending` PR Close #20212 --- packages/forms/src/model.ts | 10 ++++- packages/forms/test/form_array_spec.ts | 30 ++++++++++++++ packages/forms/test/form_control_spec.ts | 32 +++++++++++++++ packages/forms/test/form_group_spec.ts | 50 ++++++++++++++++++++++++ tools/public_api_guard/forms/forms.d.ts | 1 + 5 files changed, 122 insertions(+), 1 deletion(-) diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index d9f28b534a..0497eea645 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -357,10 +357,18 @@ export abstract class AbstractControl { /** * Marks the control as `pending`. + * + * An event will be emitted by `statusChanges` by default. + * + * Passing `false` for `emitEvent` will cause `statusChanges` to not event an event. */ - markAsPending(opts: {onlySelf?: boolean} = {}): void { + markAsPending(opts: {onlySelf?: boolean, emitEvent?: boolean} = {}): void { (this as{status: string}).status = PENDING; + if (opts.emitEvent !== false) { + (this.statusChanges as EventEmitter).emit(this.status); + } + if (this._parent && !opts.onlySelf) { this._parent.markAsPending(opts); } diff --git a/packages/forms/test/form_array_spec.ts b/packages/forms/test/form_array_spec.ts index e90ebd7986..e0a7f5f9d0 100644 --- a/packages/forms/test/form_array_spec.ts +++ b/packages/forms/test/form_array_spec.ts @@ -622,6 +622,36 @@ import {of } from 'rxjs/observable/of'; expect(c.pending).toEqual(true); expect(a.pending).toEqual(false); }); + + describe('status change events', () => { + let logger: string[]; + + beforeEach(() => { + logger = []; + a.statusChanges.subscribe((status) => logger.push(status)); + }); + + it('should emit event after marking control as pending', () => { + c.markAsPending(); + expect(logger).toEqual(['PENDING']); + }); + + it('should not emit event from parent when onlySelf is true', () => { + c.markAsPending({onlySelf: true}); + expect(logger).toEqual([]); + }); + + it('should not emit event when emitEvent = false', () => { + c.markAsPending({emitEvent: false}); + expect(logger).toEqual([]); + }); + + it('should emit event when parent is markedAsPending', () => { + a.markAsPending(); + expect(logger).toEqual(['PENDING']); + }); + }); + }); describe('valueChanges', () => { diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index 3254b3cbe9..2bc06e1171 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -1161,5 +1161,37 @@ import {FormArray} from '@angular/forms/src/model'; }); }); + describe('pending', () => { + let c: FormControl; + + beforeEach(() => { c = new FormControl('value'); }); + + it('should be false after creating a control', () => { expect(c.pending).toEqual(false); }); + + it('should be true after changing the value of the control', () => { + c.markAsPending(); + expect(c.pending).toEqual(true); + }); + + describe('status change events', () => { + let logger: string[]; + + beforeEach(() => { + logger = []; + c.statusChanges.subscribe((status) => logger.push(status)); + }); + + it('should emit event after marking control as pending', () => { + c.markAsPending(); + expect(logger).toEqual(['PENDING']); + }); + + it('should not emit event when emitEvent = false', () => { + c.markAsPending({emitEvent: false}); + expect(logger).toEqual([]); + }); + + }); + }); }); })(); diff --git a/packages/forms/test/form_group_spec.ts b/packages/forms/test/form_group_spec.ts index 3038464ede..d65bf2d8c2 100644 --- a/packages/forms/test/form_group_spec.ts +++ b/packages/forms/test/form_group_spec.ts @@ -1146,6 +1146,56 @@ import {of } from 'rxjs/observable/of'; }); + describe('pending', () => { + let c: FormControl; + let g: FormGroup; + + beforeEach(() => { + c = new FormControl('value'); + g = new FormGroup({'one': c}); + }); + + it('should be false after creating a control', () => { expect(g.pending).toEqual(false); }); + + it('should be true after changing the value of the control', () => { + c.markAsPending(); + expect(g.pending).toEqual(true); + }); + + it('should not update the parent when onlySelf = true', () => { + c.markAsPending({onlySelf: true}); + expect(g.pending).toEqual(false); + }); + + describe('status change events', () => { + let logger: string[]; + + beforeEach(() => { + logger = []; + g.statusChanges.subscribe((status) => logger.push(status)); + }); + + it('should emit event after marking control as pending', () => { + c.markAsPending(); + expect(logger).toEqual(['PENDING']); + }); + + it('should not emit event when onlySelf = true', () => { + c.markAsPending({onlySelf: true}); + expect(logger).toEqual([]); + }); + + it('should not emit event when emitEvent = false', () => { + c.markAsPending({emitEvent: false}); + expect(logger).toEqual([]); + }); + + it('should emit event when parent is markedAsPending', () => { + g.markAsPending(); + expect(logger).toEqual(['PENDING']); + }); + }); + }); }); })(); diff --git a/tools/public_api_guard/forms/forms.d.ts b/tools/public_api_guard/forms/forms.d.ts index d7f3036d3e..a718a14a68 100644 --- a/tools/public_api_guard/forms/forms.d.ts +++ b/tools/public_api_guard/forms/forms.d.ts @@ -38,6 +38,7 @@ export declare abstract class AbstractControl { }): void; markAsPending(opts?: { onlySelf?: boolean; + emitEvent?: boolean; }): void; markAsPristine(opts?: { onlySelf?: boolean;