docs(forms-validation): edit copy & add explanation of useExisting (#3372)
This commit is contained in:
parent
2d61df1c27
commit
5a6360f6a2
|
@ -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—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 — `FormsModule` and `ReactiveFormsModule` —
|
Angular has two different forms modules—`FormsModule` and
|
||||||
that correspond with the two approaches to form development.
|
`ReactiveFormsModule`—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`—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`.
|
||||||
|
|
||||||
|
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 —
|
While not difficult, this takes more time, work and
|
||||||
factors that tend to diminish test code coverage and quality.
|
skill—factors that tend to diminish test code
|
||||||
|
coverage and quality.
|
||||||
|
|
Loading…
Reference in New Issue