Skip to content

Commit

Permalink
feat(forms): Improve typings form (async)Validators
Browse files Browse the repository at this point in the history
With this commit, AsyncValidatorFn cannot be passed as ValidatorFn  anymore in FormControl.

fixes: angular#48676
  • Loading branch information
JeanMeche committed Mar 26, 2023
1 parent 2dbf3e0 commit da189de
Show file tree
Hide file tree
Showing 5 changed files with 30 additions and 8 deletions.
5 changes: 4 additions & 1 deletion goldens/public-api/forms/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -905,7 +905,10 @@ export interface Validator {
// @public
export interface ValidatorFn {
// (undocumented)
(control: AbstractControl): ValidationErrors | null;
(control: AbstractControl): (ValidationErrors & {
then?: never;
subscribe?: never;
}) | null;
}

// @public
Expand Down
5 changes: 4 additions & 1 deletion packages/forms/src/directives/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -482,10 +482,13 @@ export class EmailValidator extends AbstractValidatorDirective {
* A function that receives a control and synchronously returns a map of
* validation errors if present, otherwise null.
*
* Error objects with a `then` key or a `subscribe` key are not valid.
* They describe an async validator returning respectively a Promise or an Observable.
*
* @publicApi
*/
export interface ValidatorFn {
(control: AbstractControl): ValidationErrors|null;
(control: AbstractControl): (ValidationErrors&{then?: never, subscribe?: never})|null;
}

/**
Expand Down
11 changes: 9 additions & 2 deletions packages/forms/src/form_builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

import {inject, Injectable} from '@angular/core';

import {AsyncValidatorFn, ValidatorFn} from './directives/validators';
import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators';
import {AbstractControl, AbstractControlOptions, FormHooks} from './model/abstract_model';
import {FormArray, UntypedFormArray} from './model/form_array';
import {FormControl, FormControlOptions, FormControlState, UntypedFormControl} from './model/form_control';
Expand All @@ -22,10 +22,17 @@ function isAbstractControlOptions(options: AbstractControlOptions|{[key: string]
(options as AbstractControlOptions).updateOn !== undefined);
}

// Sometimes we might need this base signature for ValidatorFn because of compiler limitations
// see https://github.com/microsoft/TypeScript/issues/32608
export interface UnsafeValidatorFn {
(control: AbstractControl): ValidationErrors|null;
}

/**
* The union of all validator types that can be accepted by a ControlConfig.
*/
type ValidatorConfig = ValidatorFn|AsyncValidatorFn|ValidatorFn[]|AsyncValidatorFn[];

type ValidatorConfig = UnsafeValidatorFn|AsyncValidatorFn|UnsafeValidatorFn[]|AsyncValidatorFn[];

/**
* The compiler may not always be able to prove that the elements of the control config are a tuple
Expand Down
15 changes: 12 additions & 3 deletions packages/forms/test/form_control_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,15 @@ describe('FormControl', () => {
expect(c.hasValidator(minValidator)).toEqual(true);
expect(c.hasValidator(Validators.min(5))).toEqual(false);
});

it('should error if passed an async validator instead of a validator', fakeAsync(() => {
// @ts-expect-error
const c = new FormControl('value', asyncValidator('expected'));
tick();

expect(c.valid).toEqual(false);
expect(c.errors).not.toEqual({'async': true});
}));
});

describe('asyncValidator', () => {
Expand Down Expand Up @@ -590,16 +599,16 @@ describe('FormControl', () => {
}));

it('should clear async validators', fakeAsync(() => {
const c = new FormControl('value', [asyncValidator('expected'), otherAsyncValidator]);
const c = new FormControl('value', [], [asyncValidator('expected'), otherAsyncValidator]);

c.clearValidators();
c.clearAsyncValidators();

expect(c.asyncValidator).toEqual(null);
}));

it('should not change validity state if control is disabled while async validating',
fakeAsync(() => {
const c = new FormControl('value', [asyncValidator('expected')]);
const c = new FormControl('value', [], [asyncValidator('expected')]);
c.disable();
tick();
expect(c.status).toEqual('DISABLED');
Expand Down
2 changes: 1 addition & 1 deletion packages/forms/test/form_group_spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ describe('FormGroup', () => {
let group: FormGroup;

beforeEach(waitForAsync(() => {
control = new FormControl('', null, asyncValidatorReturningObservable);
control = new FormControl('', [], asyncValidatorReturningObservable);
group = new FormGroup({'one': control});
}));

Expand Down

0 comments on commit da189de

Please sign in to comment.