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.
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.