feat(forms): add hasValidators, addValidators, and removeValidators methods (for both sync and async) (#42838)
Several new functionalities are possible with this change: the most requested is that callers can now check whether a control has a required validator. Other uses include incrementally changing the validators set without doing an expensive operation to reset all validators. Closes #13461. PR Close #42838
This commit is contained in:
+106
-16
@@ -11,7 +11,7 @@ import {Observable} from 'rxjs';
|
||||
|
||||
import {removeListItem} from './directives/shared';
|
||||
import {AsyncValidatorFn, ValidationErrors, ValidatorFn} from './directives/validators';
|
||||
import {composeAsyncValidators, composeValidators, toObservable} from './validators';
|
||||
import {addValidators, composeAsyncValidators, composeValidators, hasValidator, makeValidatorsArray, removeValidators, toObservable} from './validators';
|
||||
|
||||
/**
|
||||
* Reports that a FormControl is valid, meaning that no errors exist in the input value.
|
||||
@@ -129,14 +129,12 @@ export interface AbstractControlOptions {
|
||||
updateOn?: 'change'|'blur'|'submit';
|
||||
}
|
||||
|
||||
|
||||
function isOptionsObj(validatorOrOpts?: ValidatorFn|ValidatorFn[]|AbstractControlOptions|
|
||||
null): validatorOrOpts is AbstractControlOptions {
|
||||
return validatorOrOpts != null && !Array.isArray(validatorOrOpts) &&
|
||||
typeof validatorOrOpts === 'object';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the base class for `FormControl`, `FormGroup`, and `FormArray`.
|
||||
*
|
||||
@@ -245,7 +243,9 @@ export abstract class AbstractControl {
|
||||
}
|
||||
|
||||
/**
|
||||
* The function that is used to determine the validity of this control synchronously.
|
||||
* Returns the function that is used to determine the validity of this control synchronously.
|
||||
* If multiple validators have been added, this will be a single composed function.
|
||||
* See `Validators.compose()` for additional information.
|
||||
*/
|
||||
get validator(): ValidatorFn|null {
|
||||
return this._composedValidatorFn;
|
||||
@@ -255,7 +255,9 @@ export abstract class AbstractControl {
|
||||
}
|
||||
|
||||
/**
|
||||
* The function that is used to determine the validity of this control asynchronously.
|
||||
* Returns the function that is used to determine the validity of this control asynchronously.
|
||||
* If multiple validators have been added, this will be a single composed function.
|
||||
* See `Validators.compose()` for additional information.
|
||||
*/
|
||||
get asyncValidator(): AsyncValidatorFn|null {
|
||||
return this._composedAsyncValidatorFn;
|
||||
@@ -425,32 +427,120 @@ export abstract class AbstractControl {
|
||||
|
||||
/**
|
||||
* Sets the synchronous validators that are active on this control. Calling
|
||||
* this overwrites any existing sync validators.
|
||||
* this overwrites any existing synchronous validators.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* If you want to add a new validator without affecting existing ones, consider
|
||||
* using `addValidators()` method instead.
|
||||
*/
|
||||
setValidators(newValidator: ValidatorFn|ValidatorFn[]|null): void {
|
||||
this._rawValidators = newValidator;
|
||||
this._composedValidatorFn = coerceToValidator(newValidator);
|
||||
setValidators(validators: ValidatorFn|ValidatorFn[]|null): void {
|
||||
this._rawValidators = validators;
|
||||
this._composedValidatorFn = coerceToValidator(validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the async validators that are active on this control. Calling this
|
||||
* overwrites any existing async validators.
|
||||
* Sets the asynchronous validators that are active on this control. Calling this
|
||||
* overwrites any existing asynchronous validators.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* If you want to add a new validator without affecting existing ones, consider
|
||||
* using `addAsyncValidators()` method instead.
|
||||
*/
|
||||
setAsyncValidators(newValidator: AsyncValidatorFn|AsyncValidatorFn[]|null): void {
|
||||
this._rawAsyncValidators = newValidator;
|
||||
this._composedAsyncValidatorFn = coerceToAsyncValidator(newValidator);
|
||||
setAsyncValidators(validators: AsyncValidatorFn|AsyncValidatorFn[]|null): void {
|
||||
this._rawAsyncValidators = validators;
|
||||
this._composedAsyncValidatorFn = coerceToAsyncValidator(validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties out the sync validator list.
|
||||
* Add a synchronous validator or validators to this control, without affecting other validators.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* Adding a validator that already exists will have no effect. If duplicate validator functions
|
||||
* are present in the `validators` array, only the first instance would be added to a form
|
||||
* control.
|
||||
*
|
||||
* @param validators The new validator function or functions to add to this control.
|
||||
*/
|
||||
addValidators(validators: ValidatorFn|ValidatorFn[]): void {
|
||||
this.setValidators(addValidators(validators, this._rawValidators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an asynchronous validator or validators to this control, without affecting other
|
||||
* validators.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* Adding a validator that already exists will have no effect.
|
||||
*
|
||||
* @param validators The new asynchronous validator function or functions to add to this control.
|
||||
*/
|
||||
addAsyncValidators(validators: AsyncValidatorFn|AsyncValidatorFn[]): void {
|
||||
this.setAsyncValidators(addValidators(validators, this._rawAsyncValidators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a synchronous validator from this control, without affecting other validators.
|
||||
* Validators are compared by function reference; you must pass a reference to the exact same
|
||||
* validator function as the one that was originally set. If a provided validator is not found,
|
||||
* it is ignored.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* @param validators The validator or validators to remove.
|
||||
*/
|
||||
removeValidators(validators: ValidatorFn|ValidatorFn[]): void {
|
||||
this.setValidators(removeValidators(validators, this._rawValidators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an asynchronous validator from this control, without affecting other validators.
|
||||
* Validators are compared by function reference; you must pass a reference to the exact same
|
||||
* validator function as the one that was originally set. If a provided validator is not found, it
|
||||
* is ignored.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
*
|
||||
* @param validators The asynchronous validator or validators to remove.
|
||||
*/
|
||||
removeAsyncValidators(validators: AsyncValidatorFn|AsyncValidatorFn[]): void {
|
||||
this.setAsyncValidators(removeValidators(validators, this._rawAsyncValidators));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a synchronous validator function is present on this control. The provided
|
||||
* validator must be a reference to the exact same function that was provided.
|
||||
*
|
||||
* @param validator The validator to check for presence. Compared by function reference.
|
||||
* @returns Whether the provided validator was found on this control.
|
||||
*/
|
||||
hasValidator(validator: ValidatorFn): boolean {
|
||||
return hasValidator(this._rawValidators, validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether an asynchronous validator function is present on this control. The provided
|
||||
* validator must be a reference to the exact same function that was provided.
|
||||
*
|
||||
* @param validator The asynchronous validator to check for presence. Compared by function
|
||||
* reference.
|
||||
* @returns Whether the provided asynchronous validator was found on this control.
|
||||
*/
|
||||
hasAsyncValidator(validator: AsyncValidatorFn): boolean {
|
||||
return hasValidator(this._rawAsyncValidators, validator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Empties out the synchronous validator list.
|
||||
*
|
||||
* When you add or remove a validator at run time, you must call
|
||||
* `updateValueAndValidity()` for the new validation to take effect.
|
||||
@@ -1065,7 +1155,7 @@ export abstract class AbstractControl {
|
||||
* console.log(control.status); // 'DISABLED'
|
||||
* ```
|
||||
*
|
||||
* The following example initializes the control with a sync validator.
|
||||
* The following example initializes the control with a synchronous validator.
|
||||
*
|
||||
* ```ts
|
||||
* const control = new FormControl('', Validators.required);
|
||||
|
||||
@@ -663,3 +663,56 @@ export function getControlAsyncValidators(control: AbstractControl): AsyncValida
|
||||
AsyncValidatorFn[]|null {
|
||||
return (control as any)._rawAsyncValidators as AsyncValidatorFn | AsyncValidatorFn[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accepts a singleton validator, an array, or null, and returns an array type with the provided
|
||||
* validators.
|
||||
*
|
||||
* @param validators A validator, validators, or null.
|
||||
* @returns A validators array.
|
||||
*/
|
||||
export function makeValidatorsArray<T extends ValidatorFn|AsyncValidatorFn>(validators: T|T[]|
|
||||
null): T[] {
|
||||
if (!validators) return [];
|
||||
return Array.isArray(validators) ? validators : [validators];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether a validator or validators array has a given validator.
|
||||
*
|
||||
* @param validators The validator or validators to compare against.
|
||||
* @param validator The validator to check.
|
||||
* @returns Whether the validator is present.
|
||||
*/
|
||||
export function hasValidator<T extends ValidatorFn|AsyncValidatorFn>(
|
||||
validators: T|T[]|null, validator: T): boolean {
|
||||
return Array.isArray(validators) ? validators.includes(validator) : validators === validator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Combines two arrays of validators into one. If duplicates are provided, only one will be added.
|
||||
*
|
||||
* @param validators The new validators.
|
||||
* @param currentValidators The base array of currrent validators.
|
||||
* @returns An array of validators.
|
||||
*/
|
||||
export function addValidators<T extends ValidatorFn|AsyncValidatorFn>(
|
||||
validators: T|T[], currentValidators: T|T[]|null): T[] {
|
||||
const current = makeValidatorsArray(currentValidators);
|
||||
const validatorsToAdd = makeValidatorsArray(validators);
|
||||
validatorsToAdd.forEach((v: T) => {
|
||||
// Note: if there are duplicate entries in the new validators array,
|
||||
// only the first one would be added to the current list of validarors.
|
||||
// Duplicate ones would be ignored since `hasValidator` would detect
|
||||
// the presence of a validator function and we update the current list in place.
|
||||
if (!hasValidator(current, v)) {
|
||||
current.push(v);
|
||||
}
|
||||
});
|
||||
return current;
|
||||
}
|
||||
|
||||
export function removeValidators<T extends ValidatorFn|AsyncValidatorFn>(
|
||||
validators: T|T[], currentValidators: T|T[]|null): T[] {
|
||||
return makeValidatorsArray(currentValidators).filter(v => !hasValidator(validators, v));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user