fix(forms): changes to status not always being emitted to statusChanges observable for async validators. (#42553)

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.

PR Close #42553
This commit is contained in:
Dylan Hunn 2021-06-10 14:41:29 -07:00 committed by Alex Rickabaugh
parent 56a0582d79
commit 7180ec9e7c
4 changed files with 51 additions and 3 deletions

View File

@ -1167,7 +1167,7 @@ export class FormControl extends AbstractControl {
// `VALID` or `INVALID`. // `VALID` or `INVALID`.
// The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent` // The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent`
// to `true` to allow that during the control creation process. // to `true` to allow that during the control creation process.
emitEvent: !!asyncValidator emitEvent: !!this.asyncValidator
}); });
} }
@ -1433,7 +1433,7 @@ export class FormGroup extends AbstractControl {
// If `asyncValidator` is present, it will trigger control status change from `PENDING` to // 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, // `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. // so we set `emitEvent` to `true` to allow that during the control creation process.
emitEvent: !!asyncValidator emitEvent: !!this.asyncValidator
}); });
} }
@ -1887,7 +1887,7 @@ export class FormArray extends AbstractControl {
// `VALID` or `INVALID`. // `VALID` or `INVALID`.
// The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent` // The status should be broadcasted via the `statusChanges` observable, so we set `emitEvent`
// to `true` to allow that during the control creation process. // to `true` to allow that during the control creation process.
emitEvent: !!asyncValidator emitEvent: !!this.asyncValidator
}); });
} }

View File

@ -1055,6 +1055,23 @@ describe('FormArray', () => {
expect(g.errors).toEqual({'async': true, 'other': true}); expect(g.errors).toEqual({'async': true, 'other': true});
expect(g.pending).toEqual(false); expect(g.pending).toEqual(false);
})); }));
it('should fire statusChanges events when async validators are added via options object',
fakeAsync(() => {
// The behavior is tested (in other spec files) for each of the model types (`FormControl`,
// `FormGroup` and `FormArray`).
let statuses: string[] = [];
// Create a form control with an async validator added via options object.
const asc = new FormArray([], {asyncValidators: [() => Promise.resolve(null)]});
// Subscribe to status changes.
asc.statusChanges.subscribe((status: any) => statuses.push(status));
// After a tick, the async validator should change status PENDING -> VALID.
tick();
expect(statuses).toEqual(['VALID']);
}));
}); });
describe('disable() & enable()', () => { describe('disable() & enable()', () => {

View File

@ -879,6 +879,22 @@ describe('FormControl', () => {
tick(); tick();
})); }));
it('should fire statusChanges events for async validators added via options object',
fakeAsync(() => {
// The behavior can be tested for each of the model types.
let statuses: string[] = [];
// Create a form control with an async validator added via options object.
const asc = new FormControl('', {asyncValidators: [() => Promise.resolve(null)]});
// Subscribe to status changes.
asc.statusChanges.subscribe((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(() => { it('should fire an event after the status has been updated to pending', fakeAsync(() => {
const c = new FormControl('old', Validators.required, asyncValidator('expected')); const c = new FormControl('old', Validators.required, asyncValidator('expected'));

View File

@ -819,6 +819,21 @@ describe('FormGroup', () => {
group = new FormGroup({'one': control}); group = new FormGroup({'one': control});
})); }));
it('should fire statusChanges events for async validators added via options object',
fakeAsync(() => {
// The behavior can be tested for each of the model types.
let statuses: string[] = [];
// Create a form control with an async validator added via options object.
const asc = new FormGroup({}, {asyncValidators: [() => Promise.resolve(null)]});
// Subscribe to status changes.
asc.statusChanges.subscribe((status: any) => statuses.push(status));
// After a tick, the async validator should change status PENDING -> VALID.
tick();
expect(statuses).toEqual(['VALID']);
}));
// TODO(kara): update these tests to use fake Async // TODO(kara): update these tests to use fake Async
it('should fire a statusChange if child has async validation change', done => { it('should fire a statusChange if child has async validation change', done => {