mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
402 lines
23 KiB
Markdown
402 lines
23 KiB
Markdown
# Validating form input
|
|
|
|
You can improve overall data quality by validating user input for accuracy and completeness.
|
|
This page shows how to validate user input from the UI and display useful validation messages, in both reactive and template-driven forms.
|
|
|
|
## Prerequisites
|
|
|
|
Before reading about form validation, you should have a basic understanding of the following.
|
|
|
|
* [TypeScript](https://www.typescriptlang.org/ "The TypeScript language") and HTML5 programming
|
|
* Fundamental concepts of [Angular application design](guide/architecture "Introduction to Angular application-design concepts")
|
|
* The [two types of forms that Angular supports](guide/forms-overview "Introduction to Angular forms")
|
|
* Basics of either [Template-driven Forms](guide/forms "Template-driven forms guide") or [Reactive Forms](guide/reactive-forms "Reactive forms guide")
|
|
|
|
<div class="alert is-helpful">
|
|
|
|
Get the complete example code for the reactive and template-driven forms used here to illustrate form validation.
|
|
Run the <live-example></live-example>.
|
|
|
|
</div>
|
|
|
|
<a id="template-driven-validation"></a>
|
|
|
|
## Validating input in template-driven forms
|
|
|
|
To add validation to a template-driven form, you add the same validation attributes as you would with [native HTML form validation](https://developer.mozilla.org/docs/Web/Guide/HTML/HTML5/Constraint_validation).
|
|
Angular uses directives to match these attributes with validator functions in the framework.
|
|
|
|
Every time the value of a form control changes, Angular runs validation and generates either a list of validation errors that results in an `INVALID` status, or null, which results in a VALID status.
|
|
|
|
You can then inspect the control's state by exporting `ngModel` to a local template variable.
|
|
The following example exports `NgModel` into a variable called `name`:
|
|
|
|
<code-example header="template/hero-form-template.component.html (name)" path="form-validation/src/app/template/hero-form-template.component.html" region="name-with-error-msg"></code-example>
|
|
|
|
Notice the following features illustrated by the example.
|
|
|
|
* The `<input>` element carries the HTML validation attributes: `required` and `minlength`.
|
|
It also carries a custom validator directive, `forbiddenName`.
|
|
For more information, see the [Custom validators](#custom-validators) section.
|
|
|
|
* `#name="ngModel"` exports `NgModel` into a local variable called `name`.
|
|
`NgModel` mirrors many of the properties of its underlying `FormControl` instance, so you can use this in the template to check for control states such as `valid` and `dirty`.
|
|
For a full list of control properties, see the [AbstractControl](api/forms/AbstractControl) API reference.
|
|
|
|
* The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if the `name` is invalid and the control is either `dirty` or `touched`.
|
|
|
|
* Each nested `<div>` can present a custom message for one of the possible validation errors.
|
|
There are messages for `required`, `minlength`, and `forbiddenName`.
|
|
|
|
<a id="dirty-or-touched"></a>
|
|
|
|
<div class="alert is-helpful">
|
|
|
|
To prevent the validator from displaying errors before the user has a chance to edit the form, you should check for either the `dirty` or `touched` states in a control.
|
|
|
|
* When the user changes the value in the watched field, the control is marked as "dirty"
|
|
* When the user blurs the form control element, the control is marked as "touched"
|
|
|
|
</div>
|
|
|
|
<a id="reactive-form-validation"></a>
|
|
|
|
## Validating input in reactive forms
|
|
|
|
In a reactive form, the source of truth is the component class.
|
|
Instead of adding validators through attributes in the template, you add validator functions directly to the form control model in the component class.
|
|
Angular then calls these functions whenever the value of the control changes.
|
|
|
|
### Validator functions
|
|
|
|
Validator functions can be either synchronous or asynchronous.
|
|
|
|
| Validator type | Details |
|
|
|:--- |:--- |
|
|
| Sync validators | Synchronous functions that take a control instance and immediately return either a set of validation errors or `null`. Pass these in as the second argument when you instantiate a `FormControl`. |
|
|
| Async validators | Asynchronous functions that take a control instance and return a Promise or Observable that later emits a set of validation errors or `null`. Pass these in as the third argument when you instantiate a `FormControl`. |
|
|
|
|
For performance reasons, Angular only runs async validators if all sync validators pass.
|
|
Each must complete before errors are set.
|
|
|
|
### Built-in validator functions
|
|
|
|
You can choose to [write your own validator functions](#custom-validators), or you can use some of Angular's built-in validators.
|
|
|
|
The same built-in validators that are available as attributes in template-driven forms, such as `required` and `minlength`, are all available to use as functions from the `Validators` class.
|
|
For a full list of built-in validators, see the [Validators](api/forms/Validators) API reference.
|
|
|
|
To update the hero form to be a reactive form, use some of the same
|
|
built-in validators —this time, in function form, as in the following example.
|
|
|
|
<a id="reactive-component-class"></a>
|
|
|
|
<code-example header="reactive/hero-form-reactive.component.ts (validator functions)" path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="form-group"></code-example>
|
|
|
|
In this example, the `name` control sets up two built-in validators —`Validators.required` and `Validators.minLength(4)`— and one custom validator, `forbiddenNameValidator`.
|
|
\(For more details see [custom validators](#custom-validators).\)
|
|
|
|
All of these validators are synchronous, so they are passed as the second argument.
|
|
Notice that you can support multiple validators by passing the functions in as an array.
|
|
|
|
This example also adds a few getter methods.
|
|
In a reactive form, you can always access any form control through the `get` method on its parent group, but sometimes it's useful to define getters as shorthand for the template.
|
|
|
|
If you look at the template for the `name` input again, it is fairly similar to the template-driven example.
|
|
|
|
<code-example header="reactive/hero-form-reactive.component.html (name with error msg)" path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="name-with-error-msg"></code-example>
|
|
|
|
This form differs from the template-driven version in that it no longer exports any directives.
|
|
Instead, it uses the `name` getter defined in the component class.
|
|
|
|
Notice that the `required` attribute is still present in the template.
|
|
Although it's not necessary for validation, it should be retained for accessibility purposes.
|
|
|
|
<a id="custom-validators"></a>
|
|
|
|
## Defining custom validators
|
|
|
|
The built-in validators don't always match the exact use case of your application, so you sometimes need to create a custom validator.
|
|
|
|
Consider the `forbiddenNameValidator` function from previous [reactive-form examples](#reactive-component-class).
|
|
Here's what the definition of that function looks like.
|
|
|
|
<code-example header="shared/forbidden-name.directive.ts (forbiddenNameValidator)" path="form-validation/src/app/shared/forbidden-name.directive.ts" region="custom-validator"></code-example>
|
|
|
|
The function is a factory that takes a regular expression to detect a *specific* forbidden name and returns a validator function.
|
|
|
|
In this sample, the forbidden name is "bob", so the validator rejects any hero name containing "bob".
|
|
Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
|
|
|
|
The `forbiddenNameValidator` factory returns the configured validator function.
|
|
That function takes an Angular control object and returns *either* null if the control value is valid *or* a validation error object.
|
|
The validation error object typically has a property whose name is the validation key, `'forbiddenName'`, and whose value is an arbitrary dictionary of values that you could insert into an error message, `{name}`.
|
|
|
|
Custom async validators are similar to sync validators, but they must instead return a Promise or observable that later emits null or a validation error object.
|
|
In the case of an observable, the observable must complete, at which point the form uses the last value emitted for validation.
|
|
|
|
<a id="adding-to-reactive-forms"></a>
|
|
|
|
### Adding custom validators to reactive forms
|
|
|
|
In reactive forms, add a custom validator by passing the function directly to the `FormControl`.
|
|
|
|
<code-example header="reactive/hero-form-reactive.component.ts (validator functions)" path="form-validation/src/app/reactive/hero-form-reactive.component.1.ts" region="custom-validator"></code-example>
|
|
|
|
<a id="adding-to-template-driven-forms"></a>
|
|
|
|
### Adding custom validators to template-driven forms
|
|
|
|
In template-driven forms, add a directive to the template, where the directive wraps the validator function.
|
|
For example, the corresponding `ForbiddenValidatorDirective` serves as a wrapper around the `forbiddenNameValidator`.
|
|
|
|
Angular recognizes the directive's role in the validation process because the directive registers itself with the `NG_VALIDATORS` provider, as shown in the following example.
|
|
`NG_VALIDATORS` is a predefined provider with an extensible collection of validators.
|
|
|
|
<code-example header="shared/forbidden-name.directive.ts (providers)" path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive-providers"></code-example>
|
|
|
|
The directive class then implements the `Validator` interface, so that it can easily integrate with Angular forms.
|
|
Here is the rest of the directive to help you get an idea of how it all comes together.
|
|
|
|
<code-example header="shared/forbidden-name.directive.ts (directive)" path="form-validation/src/app/shared/forbidden-name.directive.ts" region="directive"></code-example>
|
|
|
|
Once the `ForbiddenValidatorDirective` is ready, you can add its selector, `appForbiddenName`, to any input element to activate it.
|
|
For example:
|
|
|
|
<code-example header="template/hero-form-template.component.html (forbidden-name-input)" path="form-validation/src/app/template/hero-form-template.component.html" region="name-input"></code-example>
|
|
|
|
<div class="alert is-helpful">
|
|
|
|
Notice that the custom validation directive is instantiated with `useExisting` rather than `useClass`.
|
|
The registered validator must be *this instance* of the `ForbiddenValidatorDirective` —the instance in the form with its `forbiddenName` property bound to "bob".
|
|
|
|
If you were to replace `useExisting` with `useClass`, then you'd be registering a new class instance, one that doesn't have a `forbiddenName`.
|
|
|
|
</div>
|
|
|
|
## Control status CSS classes
|
|
|
|
Angular automatically mirrors many control properties onto the form control element as CSS classes.
|
|
Use these classes to style form control elements according to the state of the form.
|
|
The following classes are currently supported.
|
|
|
|
* `.ng-valid`
|
|
* `.ng-invalid`
|
|
* `.ng-pending`
|
|
* `.ng-pristine`
|
|
* `.ng-dirty`
|
|
* `.ng-untouched`
|
|
* `.ng-touched`
|
|
* `.ng-submitted` \(enclosing form element only\)
|
|
|
|
In the following example, the hero form uses the `.ng-valid` and `.ng-invalid` classes to
|
|
set the color of each form control's border.
|
|
|
|
<code-example header="forms.css (status classes)" path="form-validation/src/assets/forms.css"></code-example>
|
|
|
|
## Cross-field validation
|
|
|
|
A cross-field validator is a [custom validator](#custom-validators "Read about custom validators") that compares the values of different fields in a form and accepts or rejects them in combination.
|
|
For example, you might have a form that offers mutually incompatible options, so that if the user can choose A or B, but not both.
|
|
Some field values might also depend on others; a user might be allowed to choose B only if A is also chosen.
|
|
|
|
The following cross validation examples show how to do the following:
|
|
|
|
* Validate reactive or template-based form input based on the values of two sibling controls,
|
|
* Show a descriptive error message after the user interacted with the form and the validation failed.
|
|
|
|
The examples use cross-validation to ensure that heroes do not reveal their true identities by filling out the Hero Form.
|
|
The validators do this by checking that the hero names and alter egos do not match.
|
|
|
|
### Adding cross-validation to reactive forms
|
|
|
|
The form has the following structure:
|
|
|
|
<code-example format="javascript" language="javascript">
|
|
|
|
const heroForm = new FormGroup({
|
|
'name': new FormControl(),
|
|
'alterEgo': new FormControl(),
|
|
'power': new FormControl()
|
|
});
|
|
|
|
</code-example>
|
|
|
|
Notice that the `name` and `alterEgo` are sibling controls.
|
|
To evaluate both controls in a single custom validator, you must perform the validation in a common ancestor control: the `FormGroup`.
|
|
You query the `FormGroup` for its child controls so that you can compare their values.
|
|
|
|
To add a validator to the `FormGroup`, pass the new validator in as the second argument on creation.
|
|
|
|
<code-example format="javascript" language="javascript">
|
|
|
|
const heroForm = new FormGroup({
|
|
'name': new FormControl(),
|
|
'alterEgo': new FormControl(),
|
|
'power': new FormControl()
|
|
}, { validators: identityRevealedValidator });
|
|
|
|
</code-example>
|
|
|
|
The validator code is as follows.
|
|
|
|
<code-example header="shared/identity-revealed.directive.ts" path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-validator"></code-example>
|
|
|
|
The `identity` validator implements the `ValidatorFn` interface.
|
|
It takes an Angular control object as an argument and returns either null if the form is valid, or `ValidationErrors` otherwise.
|
|
|
|
The validator retrieves the child controls by calling the `FormGroup`'s [get](api/forms/AbstractControl#get) method, then compares the values of the `name` and `alterEgo` controls.
|
|
|
|
If the values do not match, the hero's identity remains secret, both are valid, and the validator returns null.
|
|
If they do match, the hero's identity is revealed and the validator must mark the form as invalid by returning an error object.
|
|
|
|
To provide better user experience, the template shows an appropriate error message when the form is invalid.
|
|
|
|
<code-example header="reactive/hero-form-template.component.html" path="form-validation/src/app/reactive/hero-form-reactive.component.html" region="cross-validation-error-message"></code-example>
|
|
|
|
This `*ngIf` displays the error if the `FormGroup` has the cross validation error returned by the `identityRevealed` validator, but only if the user finished [interacting with the form](#dirty-or-touched).
|
|
|
|
### Adding cross-validation to template-driven forms
|
|
|
|
For a template-driven form, you must create a directive to wrap the validator function.
|
|
You provide that directive as the validator using the [`NG_VALIDATORS` token](#adding-to-template-driven-forms "Read about providing validators"), as shown in the following example.
|
|
|
|
<code-example header="shared/identity-revealed.directive.ts" path="form-validation/src/app/shared/identity-revealed.directive.ts" region="cross-validation-directive"></code-example>
|
|
|
|
You must add the new directive to the HTML template.
|
|
Because the validator must be registered at the highest level in the form, the following template puts the directive on the `form` tag.
|
|
|
|
<code-example header="template/hero-form-template.component.html" path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-register-validator"></code-example>
|
|
|
|
To provide better user experience, an appropriate error message appears when the form is invalid.
|
|
|
|
<code-example header="template/hero-form-template.component.html" path="form-validation/src/app/template/hero-form-template.component.html" region="cross-validation-error-message"></code-example>
|
|
|
|
This is the same in both template-driven and reactive forms.
|
|
|
|
## Creating asynchronous validators
|
|
|
|
Asynchronous validators implement the `AsyncValidatorFn` and `AsyncValidator` interfaces.
|
|
These are very similar to their synchronous counterparts, with the following differences.
|
|
|
|
* The `validate()` functions must return a Promise or an observable,
|
|
* The observable returned must be finite, meaning it must complete at some point.
|
|
To convert an infinite observable into a finite one, pipe the observable through a filtering operator such as `first`, `last`, `take`, or `takeUntil`.
|
|
|
|
Asynchronous validation happens after the synchronous validation, and is performed only if the synchronous validation is successful.
|
|
This check lets forms avoid potentially expensive async validation processes \(such as an HTTP request\) if the more basic validation methods have already found invalid input.
|
|
|
|
After asynchronous validation begins, the form control enters a `pending` state.
|
|
Inspect the control's `pending` property and use it to give visual feedback about the ongoing validation operation.
|
|
|
|
A common UI pattern is to show a spinner while the async validation is being performed.
|
|
The following example shows how to achieve this in a template-driven form.
|
|
|
|
<code-example format="html" language="html">
|
|
|
|
<input [(ngModel)]="name" #model="ngModel" appSomeAsyncValidator>
|
|
<app-spinner *ngIf="model.pending"></app-spinner>
|
|
|
|
</code-example>
|
|
|
|
### Implementing a custom async validator
|
|
|
|
In the following example, an async validator ensures that heroes pick an alter ego that is not already taken.
|
|
New heroes are constantly enlisting and old heroes are leaving the service, so the list of available alter egos cannot be retrieved ahead of time.
|
|
To validate the potential alter ego entry, the validator must initiate an asynchronous operation to consult a central database of all currently enlisted heroes.
|
|
|
|
The following code creates the validator class, `UniqueAlterEgoValidator`, which implements the `AsyncValidator` interface.
|
|
|
|
<code-example path="form-validation/src/app/shared/alter-ego.directive.ts" region="async-validator"></code-example>
|
|
|
|
The constructor injects the `HeroesService`, which defines the following interface.
|
|
|
|
<code-example format="typescript" language="typescript">
|
|
|
|
interface HeroesService {
|
|
isAlterEgoTaken: (alterEgo: string) => Observable<boolean>;
|
|
}
|
|
|
|
</code-example>
|
|
|
|
In a real world application, the `HeroesService` would be responsible for making an HTTP request to the hero database to check if the alter ego is available.
|
|
From the validator's point of view, the actual implementation of the service is not important, so the example can just code against the `HeroesService` interface.
|
|
|
|
As the validation begins, the `UniqueAlterEgoValidator` delegates to the `HeroesService` `isAlterEgoTaken()` method with the current control value.
|
|
At this point the control is marked as `pending` and remains in this state until the observable chain returned from the `validate()` method completes.
|
|
|
|
The `isAlterEgoTaken()` method dispatches an HTTP request that checks if the alter ego is available, and returns `Observable<boolean>` as the result.
|
|
The `validate()` method pipes the response through the `map` operator and transforms it into a validation result.
|
|
|
|
The method then, like any validator, returns `null` if the form is valid, and `ValidationErrors` if it is not.
|
|
This validator handles any potential errors with the `catchError` operator.
|
|
In this case, the validator treats the `isAlterEgoTaken()` error as a successful validation, because failure to make a validation request does not necessarily mean that the alter ego is invalid.
|
|
You could handle the error differently and return the `ValidationError` object instead.
|
|
|
|
After some time passes, the observable chain completes and the asynchronous validation is done.
|
|
The `pending` flag is set to `false`, and the form validity is updated.
|
|
|
|
### Adding async validators to reactive forms
|
|
|
|
To use an async validator in reactive forms, begin by injecting the validator into the constructor of the component class.
|
|
|
|
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.2.ts" region="async-validator-inject"></code-example>
|
|
|
|
Then, pass the validator function directly to the `FormControl` to apply it.
|
|
|
|
In the following example, the `validate` function of `UniqueAlterEgoValidator` is applied to `alterEgoControl` by passing it to the control's `asyncValidators` option and binding it to the instance of `UniqueAlterEgoValidator` that was injected into `HeroFormReactiveComponent`.
|
|
The value of `asyncValidators` can be either a single async validator function, or an array of functions.
|
|
To learn more about `FormControl` options, see the [AbstractControlOptions](api/forms/AbstractControlOptions) API reference.
|
|
|
|
<code-example path="form-validation/src/app/reactive/hero-form-reactive.component.2.ts" region="async-validator-usage"></code-example>
|
|
|
|
### Adding async validators to template-driven forms
|
|
|
|
To use an async validator in template-driven forms, create a new directive and register the `NG_ASYNC_VALIDATORS` provider on it.
|
|
|
|
In the example below, the directive injects the `UniqueAlterEgoValidator` class that contains the actual validation logic and invokes it in the `validate` function, triggered by Angular when validation should happen.
|
|
|
|
<code-example path="form-validation/src/app/shared/alter-ego.directive.ts" region="async-validator-directive"></code-example>
|
|
|
|
Then, as with synchronous validators, add the directive's selector to an input to activate it.
|
|
|
|
<code-example header="template/hero-form-template.component.html (unique-alter-ego-input)" path="form-validation/src/app/template/hero-form-template.component.html" region="alterEgo-input"></code-example>
|
|
|
|
### Optimizing performance of async validators
|
|
|
|
By default, all validators run after every form value change.
|
|
With synchronous validators, this does not normally have a noticeable impact on application performance.
|
|
Async validators, however, commonly perform some kind of HTTP request to validate the control.
|
|
Dispatching an HTTP request after every keystroke could put a strain on the backend API, and should be avoided if possible.
|
|
|
|
You can delay updating the form validity by changing the `updateOn` property from `change` \(default\) to `submit` or `blur`.
|
|
|
|
With template-driven forms, set the property in the template.
|
|
|
|
<code-example format="html" language="html">
|
|
|
|
<input [(ngModel)]="name" [ngModelOptions]="{updateOn: 'blur'}">
|
|
|
|
</code-example>
|
|
|
|
With reactive forms, set the property in the `FormControl` instance.
|
|
|
|
<code-example format="typescript" language="typescript">
|
|
|
|
new FormControl('', {updateOn: 'blur'});
|
|
|
|
</code-example>
|
|
|
|
## Interaction with native HTML form validation
|
|
|
|
By default, Angular disables [native HTML form validation](https://developer.mozilla.org/docs/Web/Guide/HTML/Constraint_validation) by adding the `novalidate` attribute on the enclosing `<form>` and uses directives to match these attributes with validator functions in the framework.
|
|
If you want to use native validation **in combination** with Angular-based validation, you can re-enable it with the `ngNativeValidate` directive.
|
|
See the [API docs](api/forms/NgForm#native-dom-validation-ui) for details.
|
|
|
|
<!-- links -->
|
|
|
|
<!-- external links -->
|
|
|
|
<!-- end links -->
|
|
|
|
@reviewed 2023-09-12
|