docs(forms-validation): edit copy & add explanation of useExisting (#3372)

This commit is contained in:
Kapunahele Wong 2017-03-17 18:30:29 -04:00 committed by Jules Kremer
parent 2d61df1c27
commit 5a6360f6a2
1 changed files with 166 additions and 120 deletions

View File

@ -2,44 +2,50 @@ include ../_util-fns
a#top a#top
:marked :marked
We can improve overall data quality by validating user input for accuracy and completeness. 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 This cookbook shows 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. using first the template-driven forms and then the reactive forms approach.
.l-sub-section .l-sub-section
:marked :marked
Learn more about these choices in the [Forms chapter.](../guide/forms.html) Read more about these choices in the [Forms](../guide/forms.html)
and the [Reactive Forms](../guide/reactive-forms.html) guides.
a#toc a#toc
:marked :marked
## Table of Contents ## Contents
[Simple Template-Driven Forms](#template1) * [Simple template-driven forms](#template1)
* [Template-driven forms with validation messages in code](#template2)
[Template-Driven Forms with validation messages in code](#template2) - [Component Class](#component-class)
- [The benefits of messages in code](#improvement)
[Reactive Forms with validation in code](#reactive) - [`FormModule` and template-driven forms](#formmodule)
* [Reactive forms with validation in code](#reactive)
[Custom validation](#custom-validation) - [Switch to the `ReactiveFormsModule`](#reactive-forms-module)
- [Component template](#reactive-component-template)
[Testing](#testing) - [Component class](#reactive-component-class)
- [`FormBuilder` declaration](#formbuilder)
- [Committing hero value changes](#committing-changes)
* [Custom validation](#custom-validation)
- [Custom validation directive](#custom-validation-directive)
* [Testing considerations](#testing)
a#live-example a#live-example
:marked :marked
**Try the live example to see and download the full cookbook source code** **Try the live example to see and download the full cookbook source code.**
live-example(name="cb-form-validation" embedded img="cookbooks/form-validation/plunker.png") live-example(name="cb-form-validation" embedded img="cookbooks/form-validation/plunker.png")
.l-main-section .l-main-section
a#template1 a#template1
:marked :marked
## Simple Template-Driven Forms ## Simple template-driven forms
In the template-driven approach, you arrange In the template-driven approach, you arrange
[form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template. [form elements](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Forms_in_HTML) in the component's template.
You add Angular form directives (mostly directives beginning `ng...`) to help You add Angular form directives (mostly directives beginning `ng...`) to help
Angular construct a corresponding internal control model that implements form functionality. Angular construct a corresponding internal control model that implements form functionality.
We say that the control model is _implicit_ in the template. In template-drive forms, the control model is _implicit_ in the template.
To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation) To validate user input, you add [HTML validation attributes](https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/HTML5/Constraint_validation)
to the elements. Angular interprets those as well, adding validator functions to the control model. to the elements. Angular interprets those as well, adding validator functions to the control model.
@ -47,40 +53,41 @@ a#template1
Angular exposes information about the state of the controls including Angular exposes information about the state of the controls including
whether the user has "touched" the control or made changes and if the control values are valid. whether the user has "touched" the control or made changes and if the control values are valid.
In the first template validation example, In this first template validation example,
we add more HTML to read that control state and update the display appropriately. notice the HTML that reads the control state and updates the display appropriately.
Here's an excerpt from the template html for a single input box control bound to the hero name: Here's an excerpt from the template HTML for a single input control bound to the hero name:
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template1.component.html','name-with-error-msg','template/hero-form-template1.component.html (Hero name)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template1.component.html','name-with-error-msg','template/hero-form-template1.component.html (Hero name)')(format='.')
:marked :marked
Note the following: Note the following:
- The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`. - The `<input>` element carries the HTML validation attributes: `required`, `minlength`, and `maxlength`.
- We set the `name` attribute of the input box to `"name"` so Angular can track this input element and associate it - The `name` attribute of the input is set to `"name"` so Angular can track this input element and associate it
with an Angular form control called `name` in its internal control model. with an Angular form control called `name` in its internal control model.
- We use the `[(ngModel)]` directive to two-way data bind the input box to the `hero.name` property. - The `[(ngModel)]` directive allows two-way data binding between the input box to the `hero.name` property.
- We set a template variable (`#name`) to the value `"ngModel"` (always `ngModel`). - The template variable (`#name`) has the value `"ngModel"` (always `ngModel`).
This gives us a reference to the Angular `NgModel` directive This gives you a reference to the Angular `NgModel` directive
associated with this control that we can use _in the template_ associated with this control that you can use _in the template_
to check for control states such as `valid` and `dirty`. to check for control states such as `valid` and `dirty`.
- The `*ngIf` on `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and - The `*ngIf` on the `<div>` element reveals a set of nested message `divs` but only if there are "name" errors and
the control is either `dirty` or `touched`. the control is either `dirty` or `touched`.
- Each nested `<div>` can present a custom message for one of the possible validation errors. - Each nested `<div>` can present a custom message for one of the possible validation errors.
We've prepared messages for `required`, `minlength`, and `maxlength`. There are messages for `required`, `minlength`, and `maxlength`.
The full template repeats this kind of layout for each data entry control on the form. The full template repeats this kind of layout for each data entry control on the form.
a#why-check
.l-sub-section .l-sub-section
:marked :marked
#### Why check _dirty_ and _touched_? #### Why check _dirty_ and _touched_?
We shouldn't show errors for a new hero before the user has had a chance to edit the value. The app 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. The checks for `dirty` and `touched` prevent premature display of errors.
Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) chapter. Learn about `dirty` and `touched` in the [Forms](../guide/forms.html) guide.
:marked :marked
The component class manages the hero model used in the data binding The component class manages the hero model used in the data binding
as well as other code to support the view. as well as other code to support the view.
@ -102,23 +109,24 @@ a#template1
.l-main-section .l-main-section
a#template2 a#template2
:marked :marked
## Template-Driven Forms with validation messages in code ## Template-driven forms with validation messages in code
While the layout is straightforward, While the layout is straightforward,
there are obvious shortcomings with the way we handle validation messages: there are obvious shortcomings with the way it's handling validation messages:
* It takes a lot of HTML to represent all possible error conditions. * It takes a lot of HTML to represent all possible error conditions.
This gets out of hand when there are many controls and many validation rules. This gets out of hand when there are many controls and many validation rules.
* We're not fond of so much JavaScript logic in HTML. * There's a lot of JavaScript logic in the HTML.
* The messages are static strings, hard-coded into the template. * The messages are static strings, hard-coded into the template.
We often require dynamic messages that we should shape in code. It's easier to maintain _dynamic_ messages in the component class.
We can move the logic and the messages into the component with a few changes to In this example, you can move the logic and the messages into the component with a few changes to
the template and component. the template and component.
Here's the hero name again, excerpted from the revised template ("Template 2"), next to the original version: Here's the hero name again, excerpted from the revised template
(Template 2), next to the original version:
+makeTabs( +makeTabs(
`cb-form-validation/ts/src/app/template/hero-form-template2.component.html, `cb-form-validation/ts/src/app/template/hero-form-template2.component.html,
cb-form-validation/ts/src/app/template/hero-form-template1.component.html`, cb-form-validation/ts/src/app/template/hero-form-template1.component.html`,
@ -131,26 +139,31 @@ a#template2
- The hard-code error message `<divs>` are gone. - The hard-code error message `<divs>` are gone.
- There's a new attribute, `forbiddenName`, that is actually a custom validation directive. - There's a new attribute, `forbiddenName`, that is actually a custom validation directive.
It invalidates the control if the user enters "bob" anywhere in the name ([try it](#live-example)). It invalidates the control if the user enters "bob" in the name `<input>`([try it](#live-example)).
We discuss [custom validation directives](#custom-validation) later in this cookbook. See the [custom validation](#custom-validation) section later in this cookbook for more information
on custom validation directives.
- The `#name` template variable is gone because we no longer refer to the Angular control for this element. - The `#name` template variable is gone because the app no longer refers to the Angular control for this element.
- Binding to the new `formErrors.name` property is sufficent to display all name validation error messages. - Binding to the new `formErrors.name` property is sufficent to display all name validation error messages.
#### Component class a#component-class
The original component code stays the same. :marked
We _added_ new code to acquire the Angular form control and compose error messages. ### Component class
The original component code for Template 1 stayed the same; however,
Template 2 requires some changes in the component. This section covers the code
necessary in Template 2's component class to acquire the Angular
form control and compose error messages.
The first step is to acquire the form control that Angular created from the template by querying for it. The first step is to acquire the form control that Angular created from the template by querying for it.
Look back at the top of the component template where we set the Look back at the top of the component template at the
`#heroForm` template variable in the `<form>` element: `#heroForm` template variable in the `<form>` element:
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template1.component.html','form-tag','template/hero-form-template1.component.html (form tag)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template1.component.html','form-tag','template/hero-form-template1.component.html (form tag)')(format='.')
:marked :marked
The `heroForm` variable is a reference to the control model that Angular derived from the template. The `heroForm` variable is a reference to the control model that Angular derived from the template.
We tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query: Tell Angular to inject that model into the component class's `currentForm` property using a `@ViewChild` query:
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','view-child','template/hero-form-template2.component.ts (heroForm)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','view-child','template/hero-form-template2.component.ts (heroForm)')(format='.')
:marked :marked
@ -159,15 +172,15 @@ a#template2
- Angular `@ViewChild` queries for a template variable when you pass it - Angular `@ViewChild` queries for a template variable when you pass it
the name of that variable as a string (`'heroForm'` in this case). the name of that variable as a string (`'heroForm'` in this case).
- The `heroForm` object changes several times during the life of the component, most notably when we add a new hero. - The `heroForm` object changes several times during the life of the component, most notably when you add a new hero.
We'll have to re-inspect it periodically. Periodically inspecting it reveals these changes.
- Angular calls the `ngAfterViewChecked` [lifecycle hook method](../guide/lifecycle-hooks.html#afterview) - Angular calls the `ngAfterViewChecked` [lifecycle hook method](../guide/lifecycle-hooks.html#afterview)
when anything changes in the view. when anything changes in the view.
That's the right time to see if there's a new `heroForm` object. That's the right time to see if there's a new `heroForm` object.
- When there _is_ a new `heroForm` model, we subscribe to its `valueChanged` _Observable_ property. - When there _is_ a new `heroForm` model, `formChanged()` subscribes to its `valueChanges` _Observable_ property.
The `onValueChanged` handler looks for validation errors after every user keystroke. The `onValueChanged` handler looks for validation errors after every keystroke.
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','handler','template/hero-form-template2.component.ts (handler)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','handler','template/hero-form-template2.component.ts (handler)')(format='.')
:marked :marked
@ -179,25 +192,30 @@ a#template2
Only two hero properties have validation rules, `name` and `power`. Only two hero properties have validation rules, `name` and `power`.
The messages are empty strings when the hero data are valid. The messages are empty strings when the hero data are valid.
For each field, the handler For each field, the `onValueChanged` handler does the following:
- clears the prior error message if any - Clears the prior error message, if any.
- acquires the field's corresponding Angular form control - Acquires the field's corresponding Angular form control.
- if such a control exists _and_ its been changed ("dirty") _and_ its invalid ... - If such a control exists _and_ it's been changed ("dirty")
- the handler composes a consolidated error message for all of the control's errors. _and_ it's invalid, the handler composes a consolidated error message for all of the control's errors.
We'll need some error messages of course, a set for each validated property, one message per validation rule: Next, the component needs some error messages of course&mdash;a set for each validated property with
one message per validation rule:
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','messages','template/hero-form-template2.component.ts (messages)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.ts','messages','template/hero-form-template2.component.ts (messages)')(format='.')
:marked :marked
Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly. Now every time the user makes a change, the `onValueChanged` handler checks for validation errors and produces messages accordingly.
### Is this an improvement? a#improvement
:marked
### The benefits of messages in code
Clearly the template got substantially smaller while the component code got substantially larger. Clearly the template got substantially smaller while the component code got substantially larger.
It's not easy to see the benefit when there are just three fields and only two of them have validation rules. It's not easy to see the benefit when there are just three fields and only two of them have validation rules.
Consider what happens as we increase the number of validated fields and rules. Consider what happens as the number of validated
fields and rules increases.
In general, HTML is harder to read and maintain than code. In general, HTML is harder to read and maintain than code.
The initial template was already large and threatening to get rapidly worse as we add more validation message `<divs>`. The initial template was already large and threatening to get rapidly worse
with the addition of more validation message `<div>` elements.
After moving the validation messaging to the component, After moving the validation messaging to the component,
the template grows more slowly and proportionally. the template grows more slowly and proportionally.
@ -207,23 +225,26 @@ a#template2
Both trends are manageable. Both trends are manageable.
Now that the messages are in code, we have more flexibility. We can compose messages more intelligently. Now that the messages are in code, you have more flexibility and can compose messages more efficiently.
We can refactor the messages out of the component, perhaps to a service class that retrieves them from the server. You can refactor the messages out of the component, perhaps to a service class that retrieves them from the server.
In short, there are more opportunities to improve message handling now that text and logic have moved from template to code. In short, there are more opportunities to improve message handling now that text and logic have moved from template to code.
a#formmodule
:marked
### _FormModule_ and template-driven forms ### _FormModule_ and template-driven forms
Angular has two different forms modules &mdash; `FormsModule` and `ReactiveFormsModule` &mdash; Angular has two different forms modules&mdash;`FormsModule` and
that correspond with the two approaches to form development. `ReactiveFormsModule`&mdash;that correspond with the
Both modules come from the same `@angular/forms` library package. two approaches to form development. Both modules come
from the same `@angular/forms` library package.
We've been reviewing the "Template-driven" approach which requires the `FormsModule` You've been reviewing the "Template-driven" approach which requires the `FormsModule`.
Here's how we imported it in the `HeroFormTemplateModule`. Here's how you imported it in the `HeroFormTemplateModule`.
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template.module.ts','','template/hero-form-template.module.ts')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template.module.ts','','template/hero-form-template.module.ts')(format='.')
.l-sub-section .l-sub-section
:marked :marked
We haven't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every This guide hasn't talked about the `SharedModule` or its `SubmittedComponent` which appears at the bottom of every
form template in this cookbook. form template in this cookbook.
They're not germane to the validation story. Look at the [live example](#live-example) if you're interested. They're not germane to the validation story. Look at the [live example](#live-example) if you're interested.
@ -231,7 +252,7 @@ a#template2
.l-main-section .l-main-section
a#reactive a#reactive
:marked :marked
## Reactive Forms ## Reactive forms with validation in code
In the template-driven approach, you markup the template with form elements, validation attributes, In the template-driven approach, you markup the template with form elements, validation attributes,
and `ng...` directives from the Angular `FormsModule`. and `ng...` directives from the Angular `FormsModule`.
@ -244,30 +265,34 @@ a#reactive
This approach requires a bit more effort. *You have to write the control model and manage it*. This approach requires a bit more effort. *You have to write the control model and manage it*.
In return, you can This allows you to do the following:
* add, change, and remove validation functions on the fly * Add, change, and remove validation functions on the fly.
* manipulate the control model dynamically from within the component * Manipulate the control model dynamically from within the component.
* [test](#testing) validation and control logic with isolated unit tests. * [Test](#testing) validation and control logic with isolated unit tests.
The third cookbook sample re-writes the hero form in _reactive forms_ style. The following cookbook sample re-writes the hero form in _reactive forms_ style.
a#reactive-forms-module
:marked
### Switch to the _ReactiveFormsModule_ ### Switch to the _ReactiveFormsModule_
The reactive forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`. The reactive forms classes and directives come from the Angular `ReactiveFormsModule`, not the `FormsModule`.
The application module for the "Reactive Forms" feature in this sample looks like this: The application module for the reactive forms feature in this sample looks like this:
+makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts','','src/app/reactive/hero-form-reactive.module.ts')(format='.') +makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.module.ts','','src/app/reactive/hero-form-reactive.module.ts')(format='.')
:marked :marked
The "Reactive Forms" feature module and component are in the `src/app/reactive` folder. The reactive forms feature module and component are in the `src/app/reactive` folder.
Let's focus on the `HeroFormReactiveComponent` there, starting with its template. Focus on the `HeroFormReactiveComponent` there, starting with its template.
a#reactive-component-template
:marked
### Component template ### Component template
We begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template Begin by changing the `<form>` tag so that it binds the Angular `formGroup` directive in the template
to the `heroForm` property in the component class. to the `heroForm` property in the component class.
The `heroForm` is the control model that the component class builds and maintains. The `heroForm` is the control model that the component class builds and maintains.
+makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html','form-tag')(format='.') +makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html','form-tag')(format='.')
:marked :marked
Then we modify the template HTML elements to match the _reactive forms_ style. Next, modify the template HTML elements to match the _reactive forms_ style.
Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version: Here is the "name" portion of the template again, revised for reactive forms and compared with the template-driven version:
+makeTabs( +makeTabs(
`cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html, `cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.html,
@ -277,10 +302,11 @@ a#reactive
hero-form-template1.component.html (name #2)`) hero-form-template1.component.html (name #2)`)
:marked :marked
Key changes: Key changes are:
- the validation attributes are gone (except `required`) because we'll be validating in code. - The validation attributes are gone (except `required`) because
validating happens in code.
- `required` remains, not for validation purposes (we'll cover that in the code), - `required` remains, not for validation purposes (that's in the code),
but rather for css styling and accessibility. but rather for css styling and accessibility.
.l-sub-section .l-sub-section
@ -289,26 +315,28 @@ a#reactive
(and perhaps the `aria-required` attribute) when the control has the `required` validator function. (and perhaps the `aria-required` attribute) when the control has the `required` validator function.
Until then, apply the `required` attribute _and_ add the `Validator.required` function Until then, apply the `required` attribute _and_ add the `Validator.required` function
to the control model, as we'll do below. to the control model, as you'll see below.
:marked :marked
- the `formControlName` replaces the `name` attribute; it serves the same - The `formControlName` replaces the `name` attribute; it serves the same
purpose of correlating the input box with the Angular form control. purpose of correlating the input with the Angular form control.
- the two-way `[(ngModel)]` binding is gone. - The two-way `[(ngModel)]` binding is gone.
The reactive approach does not use data binding to move data into and out of the form controls. The reactive approach does not use data binding to move data into and out of the form controls.
We do that in code. That's all in code.
.l-sub-section .l-sub-section
:marked :marked
The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation. The retreat from data binding is a principle of the reactive paradigm rather than a technical limitation.
a#reactive-component-class
:marked :marked
### Component class ### Component class
The component class is now responsible for defining and managing the form control model. The component class is now responsible for defining and managing the form control model.
Angular no longer derives the control model from the template so we can no longer query for it. Angular no longer derives the control model from the template so you can no longer query for it.
We create the Angular form control model explicitly with the help of the `FormBuilder`. You can create the Angular form control model explicitly with
the help of the `FormBuilder` class.
Here's the section of code devoted to that process, paired with the template-driven code it replaces: Here's the section of code devoted to that process, paired with the template-driven code it replaces:
+makeTabs( +makeTabs(
@ -318,26 +346,27 @@ a#reactive
`reactive/hero-form-reactive.component.ts (FormBuilder), `reactive/hero-form-reactive.component.ts (FormBuilder),
template/hero-form-template2.component.ts (ViewChild)`) template/hero-form-template2.component.ts (ViewChild)`)
:marked :marked
- we inject the `FormBuilder` in a constructor. - Inject `FormBuilder` in a constructor.
- we call a `buildForm` method in the `ngOnInit` [lifecycle hook method](../guide/lifecycle-hooks.html#hooks-overview) - Call a `buildForm` method in the `ngOnInit` [lifecycle hook method](../guide/lifecycle-hooks.html#hooks-overview)
because that's when we'll have the hero data. We'll call it again in the `addHero` method. because that's when you'll have the hero data. Call it again in the `addHero` method.
.l-sub-section .l-sub-section
:marked :marked
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook. A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
:marked :marked
- the `buildForm` method uses the `FormBuilder` (`fb`) to declare the form control model. - The `buildForm` method uses the `FormBuilder`, `fb`, to declare the form control model.
Then it attaches the same `onValueChanged` handler (there's a one line difference) Then it attaches the same `onValueChanged` handler (there's a one line difference)
to the form's `valueChanged` event and calls it immediately to the form's `valueChanges` event and calls it immediately
to set error messages for the new control model. to set error messages for the new control model.
a#formbuilder
:marked :marked
#### _FormBuilder_ declaration #### _FormBuilder_ declaration
The `FormBuilder` declaration object specifies the three controls of the sample's hero form. The `FormBuilder` declaration object specifies the three controls of the sample's hero form.
Each control spec is a control name with an array value. Each control spec is a control name with an array value.
The first array element is the current value of the corresponding hero field. The first array element is the current value of the corresponding hero field.
The (optional) second value is a validator function or an array of validator functions. The optional second value is a validator function or an array of validator functions.
Most of the validator functions are stock validators provided by Angular as static methods of the `Validators` class. Most of the validator functions are stock validators provided by Angular as static methods of the `Validators` class.
Angular has stock validators that correspond to the standard HTML validation attributes. Angular has stock validators that correspond to the standard HTML validation attributes.
@ -349,6 +378,7 @@ a#reactive
:marked :marked
Learn more about `FormBuilder` in the [Introduction to FormBuilder](../guide/reactive-forms.html#formbuilder) section of Reactive Forms guide. Learn more about `FormBuilder` in the [Introduction to FormBuilder](../guide/reactive-forms.html#formbuilder) section of Reactive Forms guide.
a#committing-changes
:marked :marked
#### Committing hero value changes #### Committing hero value changes
@ -357,20 +387,20 @@ a#reactive
The developer decides _when and how_ to update the data model from control values. The developer decides _when and how_ to update the data model from control values.
This sample updates the model twice: This sample updates the model twice:
1. when the user submits the form 1. When the user submits the form.
1. when the user chooses to add a new hero 1. When the user adds a new hero.
The `onSubmit` method simply replaces the `hero` object with the combined values of the form: The `onSubmit()` method simply replaces the `hero` object with the combined values of the form:
+makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','on-submit')(format='.') +makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','on-submit')(format='.')
.l-sub-section .l-sub-section
:marked :marked
This example is "lucky" in that the `heroForm.value` properties _just happen_ to This example is lucky in that the `heroForm.value` properties _just happen_ to
correspond _exactly_ to the hero data object properties. correspond _exactly_ to the hero data object properties.
:marked :marked
The `addHero` method discards pending changes and creates a brand new `hero` model object. The `addHero()` method discards pending changes and creates a brand new `hero` model object.
+makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','add-hero')(format='.') +makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','add-hero')(format='.')
:marked :marked
Then it calls `buildForm` again which replaces the previous `heroForm` control model with a new one. Then it calls `buildForm()` again which replaces the previous `heroForm` control model with a new one.
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model. The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
Here's the complete reactive component file, compared to the two template-driven component files. Here's the complete reactive component file, compared to the two template-driven component files.
@ -385,18 +415,18 @@ a#reactive
.l-sub-section .l-sub-section
:marked :marked
Run the [live example](#live-example) to see how the reactive form behaves Run the [live example](#live-example) to see how the reactive form behaves,
and to compare all of the files in this cookbook sample. and to compare all of the files in this cookbook sample.
.l-main-section .l-main-section
a#custom-validation a#custom-validation
:marked :marked
## Custom validation ## Custom validation
This cookbook sample has a custom `forbiddenNamevalidator` function that's applied to both the This cookbook sample has a custom `forbiddenNamevalidator()` function that's applied to both the
template-driven and the reactive form controls. It's in the `src/app/shared` folder template-driven and the reactive form controls. It's in the `src/app/shared` folder
and declared in the `SharedModule`. and declared in the `SharedModule`.
Here's the `forbiddenNamevalidator` function itself: Here's the `forbiddenNamevalidator()` function:
+makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','custom-validator', 'shared/forbidden-name.directive.ts (forbiddenNameValidator)')(format='.') +makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','custom-validator', 'shared/forbidden-name.directive.ts (forbiddenNameValidator)')(format='.')
:marked :marked
The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name The function is actually a factory that takes a regular expression to detect a _specific_ forbidden name
@ -406,44 +436,59 @@ a#custom-validation
the validator rejects any hero name containing "bob". the validator rejects any hero name containing "bob".
Elsewhere it could reject "alice" or any name that the configuring regular expression matches. Elsewhere it could reject "alice" or any name that the configuring regular expression matches.
The `forbiddenNamevalidator` factory returns the configured validator function. The `forbiddenNameValidator` factory returns the configured validator function.
That function takes an Angular control object and returns _either_ That function takes an Angular control object and returns _either_
null if the control value is valid _or_ a validation error object. 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') 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 we could insert into an error message (`{name}`). and whose value is an arbitrary dictionary of values that you could insert into an error message (`{name}`).
.l-sub-section a#custom-validation-directive
:marked
Learn more about validator functions in a _forthcoming_ chapter on custom form validation.
:marked :marked
#### Custom validation directive ### Custom validation directive
In the reactive forms component we added a configured `forbiddenNamevalidator` In the reactive forms component, the `'name'` control's validator function list
to the bottom of the `'name'` control's validator function list. has a `forbiddenNameValidator` at the bottom.
+makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','name-validators', 'reactive/hero-form-reactive.component.ts (name validators)')(format='.') +makeExample('cb-form-validation/ts/src/app/reactive/hero-form-reactive.component.ts','name-validators', 'reactive/hero-form-reactive.component.ts (name validators)')(format='.')
:marked :marked
In the template-driven component template, we add the selector (`forbiddenName`) of a custom _attribute directive_ to the name's input box In the _template-driven_ example, the `<input>` has the selector (`forbiddenName`)
and configured it to reject "bob". of a custom _attribute directive_, which rejects "bob".
+makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.html','name-input', 'template/hero-form-template2.component.html (name input)')(format='.') +makeExample('cb-form-validation/ts/src/app/template/hero-form-template2.component.html','name-input', 'template/hero-form-template2.component.html (name input)')(format='.')
:marked :marked
The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNamevalidator`. The corresponding `ForbiddenValidatorDirective` is a wrapper around the `forbiddenNameValidator`.
Angular forms recognizes the directive's role in the validation process because the directive registers itself Angular `forms` recognizes the directive's role in the validation process because the directive registers itself
with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives. with the `NG_VALIDATORS` provider, a provider with an extensible collection of validation directives.
+makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','directive-providers', 'shared/forbidden-name.directive.ts (providers)')(format='.') +makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','directive-providers', 'shared/forbidden-name.directive.ts (providers)')(format='.')
:marked :marked
The rest of the directive is unremarkable and we present it here without further comment. Here is the rest of the directive to help you get an idea of how it all comes together:
+makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','directive', 'shared/forbidden-name.directive.ts (directive)') +makeExample('cb-form-validation/ts/src/app/shared/forbidden-name.directive.ts','directive', 'shared/forbidden-name.directive.ts (directive)')
:marked :marked
.l-sub-section .l-sub-section
:marked :marked
See the [Attribute Directives](../guide/attribute-directives.html) chapter. If you are familiar with Angular validations, you may have noticed
that the custom validation directive is instantiated with `useExisting`
rather than `useClass`. The registered validator must be _this instance_ of
the `ForbiddenValidatorDirective`&mdash;the instance in the form with
its `forbiddenName` property bound to “bob". If you were to replace
`useExisting` with `useClass`, then youd be registering a new class instance, one that
doesnt have a `forbiddenName`.
To see this in action, run the example and then type “bob” in the name of Hero Form 2.
Notice that you get a validation error. Now change from `useExisting` to `useClass` and try again.
This time, when you type “bob”, there's no "bob" error message.
:marked
.l-sub-section
:marked
For more information on attaching behavior to elements,
see [Attribute Directives](../guide/attribute-directives.html).
.l-main-section .l-main-section
a#testing a#testing
:marked :marked
## Testing Considerations ## Testing Considerations
We can write _isolated unit tests_ of validation and control logic in _Reactive Forms_. You can write _isolated unit tests_ of validation and control logic in _Reactive Forms_.
_Isolated unit tests_ probe the component class directly, independent of its _Isolated unit tests_ probe the component class directly, independent of its
interactions with its template, the DOM, other dependencies, or Angular itself. interactions with its template, the DOM, other dependencies, or Angular itself.
@ -451,11 +496,12 @@ a#testing
Such tests have minimal setup, are quick to write, and easy to maintain. Such tests have minimal setup, are quick to write, and easy to maintain.
They do not require the `Angular TestBed` or asynchronous testing practices. They do not require the `Angular TestBed` or asynchronous testing practices.
That's not possible with _Template-driven_ forms. That's not possible with _template-driven_ forms.
The template-driven approach relies on Angular to produce the control model and The template-driven approach relies on Angular to produce the control model and
to derive validation rules from the HTML validation attributes. to derive validation rules from the HTML validation attributes.
You must use the `Angular TestBed` to create component test instances, You must use the `Angular TestBed` to create component test instances,
write asynchronous tests, and interact with the DOM. write asynchronous tests, and interact with the DOM.
While not difficult, this takes more time, work and skill &mdash; While not difficult, this takes more time, work and
factors that tend to diminish test code coverage and quality. skill&mdash;factors that tend to diminish test code
coverage and quality.