189 lines
11 KiB
Plaintext
189 lines
11 KiB
Plaintext
include ../_util-fns
|
|
|
|
<a id="top"></a>
|
|
:marked
|
|
We want our data to be accurate and complete. By helping the user enter
|
|
appropriate data and confirming that data is valid, we can improve the quality of that incoming data.
|
|
|
|
In this cookbook we show how to validate data and display useful validation messages using first the
|
|
template-driven forms and then the reactive forms approach.
|
|
|
|
An Angular component is comprised of a template and a component class containing the code that drives the template.
|
|
The first example demonstrates how to validate data using only the template. The second technique
|
|
moves the validation logic out of the template and into the component class, giving you more control and better unit testing.
|
|
|
|
In these examples we use the form created in the [Forms chapter.](../guide/forms.html)
|
|
|
|
<a id="toc"></a>
|
|
:marked
|
|
## Table of contents
|
|
|
|
[Template-Driven Forms Approach](#template-driven)
|
|
|
|
[Reactive Forms Approach](#model-driven)
|
|
|
|
:marked
|
|
**See the [live example](/resources/live-examples/cb-validation/ts/plnkr.html)**.
|
|
|
|
.l-main-section
|
|
<a id="template-driven"></a>
|
|
:marked
|
|
## Template-Driven Forms Approach
|
|
|
|
Using the template-driven approach to form validation, the validation is defined in the template.
|
|
Each control on the form defines its validation, binding, and validation messages in the template.
|
|
|
|
+makeExample('cb-validation/ts/app/hero-form-template.component.html','name-with-error-msg','app/hero-form-template.component.html')
|
|
|
|
:marked
|
|
Here we define a standard label and set up an input box for validation of the hero name as follows:
|
|
- Add the desired HTML validation attributes. In this example,
|
|
we add `required`, `minlength`, and `maxlength` attributes on the input box to define the validation rules.
|
|
- Set the `name` attribute of the input box. This is required for Angular to track this input element.
|
|
- Use `ngModel` binding to set the default value and track the user's changes to the input box.
|
|
This registers the input box as a control that is associated with the `ngForm` directive.
|
|
- Define a template reference variable (`name` in this example) that references the registered control.
|
|
We use this variable to reference this control when checking the control state, such as `valid` or `dirty`.
|
|
|
|
Next we define a `div` element for the validation error messages.
|
|
We use the template reference variable to determine whether to display one of the validation messages.
|
|
- We use `*ngIf` to check whether the control has errors and whether it is `dirty` or `touched` before
|
|
displaying the validation block `div`.
|
|
- We then have a separate `div` for each possible validation error. In our example, we defined validation for
|
|
`required`, `minlength`, and `maxlength`, so we add one `div` for each one. Each `div` is marked with `[hidden]`
|
|
so the validation message is hidden unless the control has the specified error.
|
|
|
|
Repeat for each data entry control on the form.
|
|
.l-sub-section
|
|
:marked
|
|
Adding the check for `dirty` or `touched` prevents display of errors before the user has a chance to edit the
|
|
value. This is most useful when adding new data, such as a new hero.
|
|
|
|
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter.
|
|
:marked
|
|
The component class manages the model used in the data binding. And provides any other code required by the view.
|
|
|
|
+makeExample('cb-validation/ts/app/hero-form-template.component.ts','class','app/hero-form-template.component.ts')
|
|
|
|
:marked
|
|
Use this template-driven validation technique when working with simple forms with simple validation scenarios.
|
|
|
|
Here's the complete solution for the template-driven approach:
|
|
|
|
+makeTabs(
|
|
`cb-validation/ts/app/main.ts,
|
|
cb-validation/ts/app/app.module.ts,
|
|
cb-validation/ts/app/app.component.ts,
|
|
cb-validation/ts/app/hero.ts,
|
|
cb-validation/ts/app/hero-form-template.component.html,
|
|
cb-validation/ts/app/hero-form-template.component.ts`,
|
|
'',
|
|
'app/main.ts, app/app.module.ts, app/app.component.ts, app/hero.ts, app/hero-form-template.component.html, app/hero-form-template.component.ts' )
|
|
|
|
.l-main-section
|
|
<a id="model-driven"></a>
|
|
:marked
|
|
## Reactive Forms Approach
|
|
|
|
The alternate way to implement forms in Angular is to use reactive forms, previously called `model-driven forms`.
|
|
Using the reactive forms approach to form validation, 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-validation/ts/app/hero-form-model.component.ts','class','app/hero-form-model.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-validation/ts/app/hero-form-model.component.html','name-with-error-msg','app/hero-form-model.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's the complete solution for the reactive forms approach:
|
|
|
|
+makeTabs(
|
|
`cb-validation/ts/app/main.ts,
|
|
cb-validation/ts/app/app.module.ts,
|
|
cb-validation/ts/app/app.component.ts,
|
|
cb-validation/ts/app/hero.ts,
|
|
cb-validation/ts/app/hero-form-model.component.html,
|
|
cb-validation/ts/app/hero-form-model.component.ts`,
|
|
'',
|
|
'app/main.ts, app/app.module.ts, app/app.component.ts, app/hero.ts, app/hero-form-model.component.html, app/hero-form-model.component.ts' )
|
|
|
|
:marked
|
|
[Back to top](#top) |