199 lines
10 KiB
Plaintext
199 lines
10 KiB
Plaintext
include ../_util-fns
|
|
|
|
<a id="top"></a>
|
|
:marked
|
|
We can improve overall data quality by validating user input for accuracy and completeness.
|
|
|
|
In this cookbook we show how to validate user input in the UI and display useful validation messages
|
|
using first the template-driven forms and then the reactive forms approach.
|
|
|
|
An Angular component consists of a template and a component class containing the code that drives the template.
|
|
The first example demonstrates input validation entirely within the template.
|
|
The second example moves the validation logic out of the template and into the component class,
|
|
giving the developer more control and easier unit testing.
|
|
|
|
Both examples are based on the the sample form in the [Forms chapter.](../guide/forms.html)
|
|
|
|
<a id="toc"></a>
|
|
:marked
|
|
## Contents
|
|
|
|
[Template-Driven Forms Approach](#template-driven)
|
|
|
|
[Reactive Forms Approach](#reactive)
|
|
|
|
**Try the live example**
|
|
<live-example name="cb-form-validation" embedded img="cookbooks/form-validation/plunker.png"></live-example>
|
|
|
|
.l-main-section
|
|
<a id="template-driven"></a>
|
|
:marked
|
|
## Template-Driven Forms
|
|
|
|
In the template-driven approach,
|
|
each control on the form defines its own validation and validation messages in the template.
|
|
|
|
Here's an excerpt from the template html for a single input box control bound to the hero name:
|
|
+makeExample('cb-form-validation/ts/app/template/hero-form-template.component.html','name-with-error-msg','app/template/hero-form-template.component.html (Hero name)')
|
|
|
|
:marked
|
|
Note the following:
|
|
- The `<input>` element implements the validation rules as HTML validation attributes: `required`, `minlength`, and `maxlength`.
|
|
|
|
- Set the `name` attribute of the input box so Angular can track this input element.
|
|
|
|
- The `[(ngModel)]` two-way data binding to the hero's name in the `model.name` property also
|
|
registers the input box as a control associated with the implicit `NgForm` directive.
|
|
|
|
- A template variable (`#name`) is a reference to this control.
|
|
that we can check for control states such as `valid` or `dirty`.
|
|
The template variable value is always `ngModel`.
|
|
|
|
- A `<div>` element for a group of validation error messages.
|
|
The `*ngIf` reveals the error group if there are any errors and
|
|
the control is either `dirty` or `touched`.
|
|
|
|
- Within the error group are separate `<div>` elements for each possible validation error.
|
|
Here we've prepared messages for `required`, `minlength`, and `maxlength`.
|
|
|
|
The full template repeats this kind of layout for each data entry control on the form.
|
|
.l-sub-section
|
|
:marked
|
|
We shouldn't show errors for a new hero before the user has had a chance to edit the value.
|
|
The checks for `dirty` and `touched` prevent premature display of errors.
|
|
|
|
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter.
|
|
:marked
|
|
The component class manages the hero model used in the data binding
|
|
as well as other code to support the view.
|
|
|
|
+makeExample('cb-form-validation/ts/app/template/hero-form-template.component.ts','class','app/template/hero-form-template.component.ts')
|
|
|
|
:marked
|
|
Use this template-driven validation technique when working with simple forms with simple validation scenarios.
|
|
|
|
Here are the pertinent files for the template-driven approach:
|
|
|
|
+makeTabs(
|
|
`cb-form-validation/ts/app/template/hero-form-template.module.ts,
|
|
cb-form-validation/ts/app/template/hero-form-template.component.html,
|
|
cb-form-validation/ts/app/template/hero-form-template.component.ts,
|
|
cb-form-validation/ts/app/shared/hero.ts,
|
|
cb-form-validation/ts/app/shared/submitted.component.ts`,
|
|
'',
|
|
`app/template/hero-form-template.module.ts,
|
|
app/template/hero-form-template.component.html,
|
|
app/template/hero-form-template.component.ts,
|
|
app/shared/hero.ts,
|
|
app/shared/submitted.component.ts`)
|
|
|
|
.l-main-section
|
|
<a id="reactive"></a>
|
|
:marked
|
|
## Reactive Forms
|
|
|
|
Reactive forms are an alternate approach to form validation in the validation rules are specified in the model as
|
|
defined in the component class. Defining the validation in the class instead of the template gives you more control.
|
|
You can adjust the validation based on the application state or user.
|
|
Your code then becomes the source of truth for your validation.
|
|
|
|
We also remove the data binding (`ngModel`) and validation messages from the template.
|
|
This means that we need to set the default value for each control, and we need to add code
|
|
that tracks the user's changes so we can hide/show validation messages as needed.
|
|
|
|
.alert.is-important
|
|
:marked
|
|
When moving the validation attributes out of the HTML, we are no longer aria ready. Work is being done to
|
|
address this.
|
|
|
|
+makeExample('cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts','class','app/reactive/hero-form-reactive.component.ts')
|
|
|
|
:marked
|
|
In the component's class, we define the form and our own data structures to manage definition and display of the validation messages:
|
|
- Declare a property for the form typed as a `FormGroup`.
|
|
- Declare a property for a collection that contains the *current* validation messages to display to the user.
|
|
We'll initialize this collection with one entry for each control. We'll update this collection
|
|
with appropriate validation messages when validation rules are broken.
|
|
- Declare a property for a collection that contains the set of *possible* validation messages. We'll initialize
|
|
this collection with all of the possible validation messages for each control.
|
|
- Add a constructor for the class and use dependency injection to inject in the `FormBuilder` service. We use
|
|
that service to build the form.
|
|
- In the constructor, initialize the collections.
|
|
- The `formError` collection is initialized using the control
|
|
name as the key and the current validation message as the value. When the form is first displayed, no validation messages
|
|
should appear, so the current validation message for each control is empty.
|
|
- The `validationMessages` collection is
|
|
initialized using the control name as the key and the set of possible validation messages as the value. For this example,
|
|
we hard-code in the set of validation messages. But you can retrieve these messages from an external file or
|
|
from a database table. Alternatively, you could build a service that retrieved and managed the set of validation messsages.
|
|
- Build a method to create the form. We name this method `buildForm` in our example.
|
|
The form is created in a method so it can be
|
|
called again to reset the form with different default values when adding a new hero.
|
|
- In the `buildForm` method, group the controls defined for the form using the `FormBuilder` instance. Here we define each control's name, default value, and
|
|
validation rules.
|
|
- We call this `buildForm` method from the `ngOnInit` lifecycle hook method. But in many applications, you may need to call `buildForm`
|
|
from somewhere else. For example, if the default values are coming from an http request, call the `buildForm` method
|
|
after the data is retrieved.
|
|
|
|
.l-sub-section
|
|
:marked
|
|
Learn more about `ngOnInit` in the [LifeCycle Hooks](../guide/lifecycle-hooks.html) chapter.
|
|
:marked
|
|
There is one more important thing that we need to do. We need to watch for any changes that the user makes and adjust the
|
|
validation messages appropriately. For example, if the user enters a single character into the name field, we need to
|
|
change the validation message from `Name is required` to `Name must be at least 4 characters long`. And when the user
|
|
enters the fourth character, we need to remove the message entirely.
|
|
|
|
To watch for changes, we add one additional statement to the `buildForm` method. We subscribe to the built-in
|
|
`FormGroup`'s `valueChanges` observable. Each time any control on the form is changed by the user, we receive a
|
|
notification. In our example, we then call an `onValueChanged` method to reset the validation messages.
|
|
|
|
In the `onValueChanged` method, we:
|
|
- Loop through each entry in the `formError` collection.
|
|
- Determine whether the control has an error using the control's properties and our business rules. In our example
|
|
we define that a control has an error if the control is dirty and not valid.
|
|
- We clear any prior validation messages.
|
|
- If the control has a validation error, we loop through the errors collection and concatenate the appropriate
|
|
validation messages into one message for display to the user.
|
|
|
|
We'll use the form and `formError` collection properties in the template. Notice that when using the reactive forms approach,
|
|
the amount of code required for each control in the template is signficantly reduced.
|
|
|
|
+makeExample('cb-form-validation/ts/app/reactive/hero-form-reactive.component.html','name-with-error-msg','app/reactive/hero-form-reactive.component.html')
|
|
|
|
:marked
|
|
In the template, define a standard label and set up an input box for validation as follows:
|
|
- Set the `formControlName` directive to the name of the control as defined in the `FormBuilder`'s `group` method, `name` in this example.
|
|
|
|
In this example we also used `ngClass` to set a style on required fields. This is optional and based on the styling
|
|
you select for your application.
|
|
|
|
In the `div` element for the validation messages, we use the validation messages collection (`formError` in this example) to determine whether to display a validation message.
|
|
- We use `*ngIf` to check whether the control has a validation message in the collection.
|
|
- If so, we display it to the user using interpolation.
|
|
|
|
Repeat for each data entry control on the form.
|
|
|
|
The template then has no validation logic. If there is a validation message in the collection it displays it, if not
|
|
it doesn't. All of the logic is in the component class.
|
|
|
|
Use this technique when you want better control over the validation rules and messsages.
|
|
|
|
Here are the pertinent files for the reactive forms approach:
|
|
|
|
+makeTabs(
|
|
`cb-form-validation/ts/app/reactive/hero-form-reactive.module.ts,
|
|
cb-form-validation/ts/app/reactive/hero-form-reactive.component.html,
|
|
cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts,
|
|
cb-form-validation/ts/app/shared/hero.ts,
|
|
cb-form-validation/ts/app/shared/submitted.component.ts`,
|
|
'',
|
|
`app/reactive/hero-form-reactive.module.ts,
|
|
app/reactive/hero-form-reactive.component.html,
|
|
app/reactive/hero-form-reactive.component.ts,
|
|
app/shared/hero.ts,
|
|
app/shared/submitted.component.ts`)
|
|
|
|
:marked
|
|
[Back to top](#top)
|