fix(forms): Warn on FormControls that are constructed with both options and asyncValidators.

DEPRECATED:

It is now deprecated to provide *both* `AbstractControlOption`s and an async validators argument to a FormControl. Previously, the async validators would just be silently dropped, resulting in a probably buggy forms. Now, the constructor call is deprecated, and Angular will print a warning in devmode.
This commit is contained in:
Dylan Hunn 2022-05-18 16:48:23 -07:00 committed by Alex Rickabaugh
parent 37bf6932e9
commit 0e14df697a
5 changed files with 40 additions and 1 deletions

View file

@ -285,6 +285,8 @@ export class FormBuilder {
control<T>(formState: T | FormControlState<T>, opts: FormControlOptions & {
nonNullable: true;
}): FormControl<T>;
// @deprecated (undocumented)
control<T>(formState: T | FormControlState<T>, opts: FormControlOptions, asyncValidator: AsyncValidatorFn | AsyncValidatorFn[]): FormControl<T | null>;
// (undocumented)
control<T>(formState: T | FormControlState<T>, validatorOrOpts?: ValidatorFn | ValidatorFn[] | FormControlOptions | null, asyncValidator?: AsyncValidatorFn | AsyncValidatorFn[] | null): FormControl<T | null>;
group<T extends {}>(controls: T, options?: AbstractControlOptions | null): FormGroup<{

View file

@ -73,6 +73,22 @@ export const disabledAttrWarning = `
});
`;
export const asyncValidatorsDroppedWithOptsWarning = `
It looks like you're constructing using a FormControl with both an options argument and an
async validators argument. Mixing these arguments will cause your async validators to be dropped.
You should either put all your validators in the options object, or in separate validators
arguments. For example:
// Using validators arguments
fc = new FormControl(42, Validators.required, myAsyncValidator);
// Using AbstractControlOptions
fc = new FormControl(42, {validators: Validators.required, asyncValidators: myAV});
// Do NOT mix them: async validators will be dropped!
fc = new FormControl(42, {validators: Validators.required}, /* Oops! */ myAsyncValidator);
`;
export function ngModelWarning(directiveName: string): string {
return `
It looks like you're using ngModel on the same form field as ${directiveName}.

View file

@ -214,6 +214,13 @@ export class FormBuilder {
control<T>(formState: T|FormControlState<T>, opts: FormControlOptions&{nonNullable: true}):
FormControl<T>;
/**
* @deprecated When passing an `options` argument, the `asyncValidator` argument has no effect.
*/
control<T>(
formState: T|FormControlState<T>, opts: FormControlOptions,
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[]): FormControl<T|null>;
control<T>(
formState: T|FormControlState<T>,
validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null,

View file

@ -9,7 +9,7 @@
import {EventEmitter, ɵRuntimeError as RuntimeError} from '@angular/core';
import {Observable} from 'rxjs';
import {missingControlError, missingControlValueError, noControlsError} from '../directives/reactive_errors';
import {asyncValidatorsDroppedWithOptsWarning, missingControlError, missingControlValueError, noControlsError} from '../directives/reactive_errors';
import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from '../directives/validators';
import {RuntimeErrorCode} from '../errors';
import {FormArray, FormGroup} from '../forms';
@ -88,6 +88,11 @@ export function pickAsyncValidators(
asyncValidator?: AsyncValidatorFn|AsyncValidatorFn[]|null,
validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|null): AsyncValidatorFn|
AsyncValidatorFn[]|null {
if (typeof ngDevMode === 'undefined' || ngDevMode) {
if (isOptionsObj(validatorOrOpts) && asyncValidator) {
console.warn(asyncValidatorsDroppedWithOptsWarning);
}
}
return (isOptionsObj(validatorOrOpts) ? validatorOrOpts.asyncValidators : asyncValidator) || null;
}

View file

@ -375,12 +375,21 @@ export interface ɵFormControlCtor {
*/
new<T = any>(value: FormControlState<T>|T, opts: FormControlOptions&{nonNullable: true}):
FormControl<T>;
/**
* @deprecated Use `nonNullable` instead.
*/
new<T = any>(value: FormControlState<T>|T, opts: FormControlOptions&{
initialValueIsDefault: true
}): FormControl<T>;
/**
* @deprecated When passing an `options` argument, the `asyncValidator` argument has no effect.
*/
new<T = any>(
value: FormControlState<T>|T, opts: FormControlOptions,
asyncValidator: AsyncValidatorFn|AsyncValidatorFn[]): FormControl<T|null>;
new<T = any>(
value: FormControlState<T>|T,
validatorOrOpts?: ValidatorFn|ValidatorFn[]|FormControlOptions|null,