Skip to content

Commit

Permalink
fix(forms): changes to status not always being emitted to statusChang…
Browse files Browse the repository at this point in the history
…es 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
  • Loading branch information
dylhunn authored and alxhub committed Jun 14, 2021
1 parent 56a0582 commit 7180ec9
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 3 deletions.
6 changes: 3 additions & 3 deletions packages/forms/src/model.ts
Expand Up @@ -1167,7 +1167,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
});
}

Expand Down Expand Up @@ -1433,7 +1433,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
});
}

Expand Down Expand Up @@ -1887,7 +1887,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
});
}

Expand Down
17 changes: 17 additions & 0 deletions packages/forms/test/form_array_spec.ts
Expand Up @@ -1055,6 +1055,23 @@ describe('FormArray', () => {
expect(g.errors).toEqual({'async': true, 'other': true});
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()', () => {
Expand Down
16 changes: 16 additions & 0 deletions packages/forms/test/form_control_spec.ts
Expand Up @@ -879,6 +879,22 @@ describe('FormControl', () => {
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(() => {
const c = new FormControl('old', Validators.required, asyncValidator('expected'));

Expand Down
15 changes: 15 additions & 0 deletions packages/forms/test/form_group_spec.ts
Expand Up @@ -819,6 +819,21 @@ describe('FormGroup', () => {
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
it('should fire a statusChange if child has async validation change', done => {
Expand Down

0 comments on commit 7180ec9

Please sign in to comment.