From 70be334a11b0c4249bfd85e8d0793c6535407761 Mon Sep 17 00:00:00 2001 From: Dylan Hunn Date: Thu, 10 Jun 2021 14:41:29 -0700 Subject: [PATCH] fix(forms): changes to status not always being emitted to statusChanges observable for async validators. When a FormControl, FormArray, or FormGroup is first constructed, if an async validator is attached, the `statusChanges` observable should receive a message when the validator complete (i.e. pending -> valid/invalid). If the validator was provided as part of the constructor options, it was not fired at construction time, which is fixed in this PR. Fixes #35309. --- packages/forms/src/model.ts | 9 ++++++--- packages/forms/test/form_control_spec.ts | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/packages/forms/src/model.ts b/packages/forms/src/model.ts index ae6550a82b578..82effc09a8347 100644 --- a/packages/forms/src/model.ts +++ b/packages/forms/src/model.ts @@ -740,6 +740,7 @@ export abstract class AbstractControl { } } + debugger; if (opts.emitEvent !== false) { (this.valueChanges as EventEmitter).emit(this.value); (this.statusChanges as EventEmitter).emit(this.status); @@ -765,6 +766,7 @@ export abstract class AbstractControl { } private _runAsyncValidator(emitEvent?: boolean): void { + debugger; if (this.asyncValidator) { (this as {status: string}).status = PENDING; this._hasOwnPendingAsyncValidator = true; @@ -927,6 +929,7 @@ export abstract class AbstractControl { /** @internal */ _updateControlsErrors(emitEvent: boolean): void { + debugger; (this as {status: string}).status = this._calculateStatus(); if (emitEvent) { @@ -1167,7 +1170,7 @@ export class FormControl extends AbstractControl { // `VALID` or `INVALID`. // The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent` // to `true` to allow that during the control creation process. - emitEvent: !!asyncValidator + emitEvent: !!this.asyncValidator }); } @@ -1433,7 +1436,7 @@ export class FormGroup extends AbstractControl { // If `asyncValidator` is present, it will trigger control status change from `PENDING` to // `VALID` or `INVALID`. The status should be broadcasted via the `statusChanges` observable, // so we set `emitEvent` to `true` to allow that during the control creation process. - emitEvent: !!asyncValidator + emitEvent: !!this.asyncValidator }); } @@ -1887,7 +1890,7 @@ export class FormArray extends AbstractControl { // `VALID` or `INVALID`. // The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent` // to `true` to allow that during the control creation process. - emitEvent: !!asyncValidator + emitEvent: !!this.asyncValidator }); } diff --git a/packages/forms/test/form_control_spec.ts b/packages/forms/test/form_control_spec.ts index d4344e8ce5f1c..5daddd20f4e63 100644 --- a/packages/forms/test/form_control_spec.ts +++ b/packages/forms/test/form_control_spec.ts @@ -10,6 +10,7 @@ import {fakeAsync, tick} from '@angular/core/testing'; import {FormControl, FormGroup, Validators} from '@angular/forms'; import {FormArray} from '@angular/forms/src/model'; +import {expect} from '@angular/platform-browser/testing/src/matchers'; import {asyncValidator, asyncValidatorReturningObservable} from './util'; (function() { @@ -879,6 +880,25 @@ describe('FormControl', () => { tick(); })); + it('should fire statusChanges events for async validators transitioning from pending to valid', + fakeAsync(() => { + let statuses: string[] = []; + + // Create a form control with an async validator. + const asc = new FormControl('', {asyncValidators: [control => Promise.resolve(null)]}); + + // Subscribe to status changes. + asc.statusChanges.subscribe({ + next: (status: any) => { + statuses.push(status); + } + }); + + // After a tick, the async validator should change status PENDING -> VALID. + tick(); + expect(statuses).toEqual(['VALID']); + })); + it('should fire an event after the status has been updated to pending', fakeAsync(() => { const c = new FormControl('old', Validators.required, asyncValidator('expected'));