Skip to content

Commit 07a1aa3

Browse files
JeanMechedylhunn
authored andcommitted
feat(forms): Improve typings form (async)Validators (#48679)
With this commit, AsyncValidatorFn cannot be passed as ValidatorFn anymore in FormControl. fixes: #48676 PR Close #48679
1 parent 39a648f commit 07a1aa3

File tree

5 files changed

+30
-8
lines changed

5 files changed

+30
-8
lines changed

goldens/public-api/forms/index.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -905,7 +905,10 @@ export interface Validator {
905905
// @public
906906
export interface ValidatorFn {
907907
// (undocumented)
908-
(control: AbstractControl): ValidationErrors | null;
908+
(control: AbstractControl): (ValidationErrors & {
909+
then?: never;
910+
subscribe?: never;
911+
}) | null;
909912
}
910913

911914
// @public

packages/forms/src/directives/validators.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -482,10 +482,13 @@ export class EmailValidator extends AbstractValidatorDirective {
482482
* A function that receives a control and synchronously returns a map of
483483
* validation errors if present, otherwise null.
484484
*
485+
* Error objects with a `then` key or a `subscribe` key are not valid.
486+
* They describe an async validator returning respectively a Promise or an Observable.
487+
*
485488
* @publicApi
486489
*/
487490
export interface ValidatorFn {
488-
(control: AbstractControl): ValidationErrors|null;
491+
(control: AbstractControl): (ValidationErrors&{then?: never, subscribe?: never})|null;
489492
}
490493

491494
/**

packages/forms/src/form_builder.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

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

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

25+
// Sometimes we might need this base signature for ValidatorFn because of compiler limitations
26+
// see https://github.com/microsoft/TypeScript/issues/32608
27+
export interface UnsafeValidatorFn {
28+
(control: AbstractControl): ValidationErrors|null;
29+
}
30+
2531
/**
2632
* The union of all validator types that can be accepted by a ControlConfig.
2733
*/
28-
type ValidatorConfig = ValidatorFn|AsyncValidatorFn|ValidatorFn[]|AsyncValidatorFn[];
34+
35+
type ValidatorConfig = UnsafeValidatorFn|AsyncValidatorFn|UnsafeValidatorFn[]|AsyncValidatorFn[];
2936

3037
/**
3138
* The compiler may not always be able to prove that the elements of the control config are a tuple

packages/forms/test/form_control_spec.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,15 @@ describe('FormControl', () => {
398398
expect(c.hasValidator(minValidator)).toEqual(true);
399399
expect(c.hasValidator(Validators.min(5))).toEqual(false);
400400
});
401+
402+
it('should error if passed an async validator instead of a validator', fakeAsync(() => {
403+
// @ts-expect-error
404+
const c = new FormControl('value', asyncValidator('expected'));
405+
tick();
406+
407+
expect(c.valid).toEqual(false);
408+
expect(c.errors).not.toEqual({'async': true});
409+
}));
401410
});
402411

403412
describe('asyncValidator', () => {
@@ -590,16 +599,16 @@ describe('FormControl', () => {
590599
}));
591600

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

595-
c.clearValidators();
604+
c.clearAsyncValidators();
596605

597606
expect(c.asyncValidator).toEqual(null);
598607
}));
599608

600609
it('should not change validity state if control is disabled while async validating',
601610
fakeAsync(() => {
602-
const c = new FormControl('value', [asyncValidator('expected')]);
611+
const c = new FormControl('value', [], [asyncValidator('expected')]);
603612
c.disable();
604613
tick();
605614
expect(c.status).toEqual('DISABLED');

packages/forms/test/form_group_spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -816,7 +816,7 @@ describe('FormGroup', () => {
816816
let group: FormGroup;
817817

818818
beforeEach(waitForAsync(() => {
819-
control = new FormControl('', null, asyncValidatorReturningObservable);
819+
control = new FormControl('', [], asyncValidatorReturningObservable);
820820
group = new FormGroup({'one': control});
821821
}));
822822

0 commit comments

Comments
 (0)