docs(forms): copyedits (#3149)

* - Copyedits, some from Kathy (from an early revision of the forms page).
- Removed side note detailing ngModel; now referring reader to
template-syntax page instead.

* post-review edits
This commit is contained in:
Patrice Chalin 2017-01-26 14:15:35 -08:00 committed by Filipe Silva
parent b793d1d531
commit 806c658253
3 changed files with 250 additions and 263 deletions

View File

@ -1,20 +1,19 @@
<!-- #docplaster --> <!-- #docplaster -->
<!-- #docregion final --> <!-- #docregion final -->
<div class="container"> <div class="container">
<!-- #docregion edit-div --> <!-- #docregion edit-div -->
<div [hidden]="submitted"> <div [hidden]="submitted">
<h1>Hero Form</h1> <h1>Hero Form</h1>
<!-- #docregion ngSubmit --> <!-- #docregion ngSubmit -->
<form (ngSubmit)="onSubmit()" #heroForm="ngForm"> <form (ngSubmit)="onSubmit()" #heroForm="ngForm">
<!-- #enddocregion ngSubmit --> <!-- #enddocregion ngSubmit, edit-div -->
<!-- #enddocregion edit-div -->
<div class="form-group"> <div class="form-group">
<!-- #docregion name-with-error-msg --> <!-- #docregion name-with-error-msg -->
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" class="form-control" id="name" <input type="text" class="form-control" id="name"
required required
[(ngModel)]="model.name" name="name" [(ngModel)]="model.name" name="name"
#name="ngModel" > #name="ngModel">
<!-- #docregion hidden-error-msg --> <!-- #docregion hidden-error-msg -->
<div [hidden]="name.valid || name.pristine" <div [hidden]="name.valid || name.pristine"
class="alert alert-danger"> class="alert alert-danger">
@ -27,7 +26,7 @@
<div class="form-group"> <div class="form-group">
<label for="alterEgo">Alter Ego</label> <label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" id="alterEgo" <input type="text" class="form-control" id="alterEgo"
[(ngModel)]="model.alterEgo" name="alterEgo" > [(ngModel)]="model.alterEgo" name="alterEgo">
</div> </div>
<div class="form-group"> <div class="form-group">
@ -35,7 +34,7 @@
<select class="form-control" id="power" <select class="form-control" id="power"
required required
[(ngModel)]="model.power" name="power" [(ngModel)]="model.power" name="power"
#power="ngModel" > #power="ngModel">
<option *ngFor="let pow of powers" [value]="pow">{{pow}}</option> <option *ngFor="let pow of powers" [value]="pow">{{pow}}</option>
</select> </select>
<div [hidden]="power.valid || power.pristine" class="alert alert-danger"> <div [hidden]="power.valid || power.pristine" class="alert alert-danger">
@ -185,25 +184,25 @@
<!-- EXTRA MATERIAL FOR DOCUMENTATION --> <!-- EXTRA MATERIAL FOR DOCUMENTATION -->
<hr> <hr>
<!-- #docregion ngModelName-1 --> <!-- #docregion ngModelName-1 -->
<input type="text" class="form-control" id="name" <input type="text" class="form-control" id="name"
required required
[(ngModel)]="model.name" name="name"> [(ngModel)]="model.name" name="name">
TODO: remove this: {{model.name}} TODO: remove this: {{model.name}}
<!-- #enddocregion ngModelName-1 --> <!-- #enddocregion ngModelName-1 -->
<hr> <hr>
<!-- #docregion ngModel-3--> <!-- #docregion ngModel-3-->
<input type="text" class="form-control" id="name" <input type="text" class="form-control" id="name"
required required
[ngModel]="model.name" name="name" [ngModel]="model.name" name="name"
(ngModelChange)="model.name = $event" > (ngModelChange)="model.name = $event">
TODO: remove this: {{model.name}} TODO: remove this: {{model.name}}
<!-- #enddocregion ngModel-3--> <!-- #enddocregion ngModel-3-->
<hr> <hr>
<!-- #docregion ngModelName-2 --> <!-- #docregion ngModelName-2 -->
<input type="text" class="form-control" id="name" <input type="text" class="form-control" id="name"
required required
[(ngModel)]="model.name" name="name" [(ngModel)]="model.name" name="name"
#spy > #spy>
<br>TODO: remove this: {{spy.className}} <br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ngModelName-2 --> <!-- #enddocregion ngModelName-2 -->

View File

@ -1,6 +1,5 @@
// #docplaster // #docplaster
// #docregion // #docregion , v1, final
// #docregion first, final
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from './hero';
@ -26,21 +25,29 @@ export class HeroFormComponent {
// #enddocregion final // #enddocregion final
// TODO: Remove this when we're done // TODO: Remove this when we're done
get diagnostic() { return JSON.stringify(this.model); } get diagnostic() { return JSON.stringify(this.model); }
// #enddocregion first // #enddocregion v1
// #docregion final // #docregion final, new-hero
// #docregion new-hero
newHero() { newHero() {
this.model = new Hero(42, '', ''); this.model = new Hero(42, '', '');
} }
// #enddocregion new-hero // #enddocregion final, new-hero
// #enddocregion final
skyDog(): Hero {
// #docregion SkyDog
let myHero = new Hero(42, 'SkyDog',
'Fetch any object at any distance',
'Leslie Rollover');
console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
// #enddocregion SkyDog
return myHero;
}
//////// NOT SHOWN IN DOCS //////// //////// NOT SHOWN IN DOCS ////////
// Reveal in html: // Reveal in html:
// Name via form.controls = {{showFormControls(heroForm)}} // Name via form.controls = {{showFormControls(heroForm)}}
showFormControls(form: any) { showFormControls(form: any) {
return form && form.controls['name'] && return form && form.controls['name'] &&
// #docregion form-controls // #docregion form-controls
form.controls['name'].value; // Dr. IQ form.controls['name'].value; // Dr. IQ
@ -49,6 +56,5 @@ export class HeroFormComponent {
///////////////////////////// /////////////////////////////
// #docregion first, final // #docregion v1, final
} }
// #enddocregion first, final

View File

@ -2,7 +2,7 @@ include ../_util-fns
:marked :marked
Weve all used a form to log in, submit a help request, place an order, book a flight, Weve all used a form to log in, submit a help request, place an order, book a flight,
schedule a meeting and perform countless other data entry tasks. schedule a meeting, and perform countless other data entry tasks.
Forms are the mainstay of business applications. Forms are the mainstay of business applications.
Any seasoned web developer can slap together an HTML form with all the right tags. Any seasoned web developer can slap together an HTML form with all the right tags.
@ -15,91 +15,89 @@ include ../_util-fns
**two-way data binding, change tracking, validation, and error handling** **two-way data binding, change tracking, validation, and error handling**
... which we shall cover in this guide on Angular forms. ... which we shall cover in this guide on Angular forms.
We will build a simple form from scratch, one step at a time. Along the way we'll learn how to We will build a simple form from scratch, one step at a time. Along the way we'll learn how to:
- build an Angular form with a component and template - Build an Angular form with a component and template
- Use `ngModel` to create two-way data bindings for reading and writing input control values
- two-way data bind with `[(ngModel)]` syntax for reading and writing values to input controls - Track state changes and the validity of form controls
- Provide visual feedback using special CSS classes that track the state of the controls
- track the change state and validity of form controls using `ngModel` in combination with a form - Display validation errors to users and enable/disable form controls
- Share information across HTML elements using template reference variables
- provide strong visual feedback using special CSS classes that track the state of the controls
- display validation errors to users and enable/disable form controls
- use [template reference variables](./template-syntax.html#ref-vars) for sharing information among HTML elements
Run the <live-example></live-example>. Run the <live-example></live-example>.
.l-main-section .l-main-section
:marked :marked
## Template-Driven Forms ## Template-driven forms
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
the form-specific directives and techniques described in this guide. the form-specific directives and techniques described in this guide.
.l-sub-section .l-sub-section
:marked :marked
That's not the only way to create a form but it's the way we'll cover in this guide. That's not the only way to create a form but it's the way we'll cover in this guide.
:marked :marked
We can build almost any form we need with an Angular template &mdash; login forms, contact forms ... pretty much any business forms. We can build almost any form we need with an Angular template &mdash; login forms, contact forms, pretty much any business form.
We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
It will be pretty easy because Angular handles many of the repetitive, boiler plate tasks we'd It will be pretty easy because Angular handles many of the repetitive, boilerplate tasks we'd
otherwise wrestle with ourselves. otherwise wrestle with ourselves.
We'll discuss and learn to build the following template-driven form: We'll discuss and learn to build a template-driven form that looks like this:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form") img(src="/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form")
:marked :marked
Here at the *Hero Employment Agency* we use this form to maintain personal information about the Here at the *Hero Employment Agency* we use this form to maintain personal information about heroes.
heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis! Every hero needs a job. It's our company mission to match the right hero with the right crisis!
Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot. Two of the three fields on this form are required. Required fields have a green bar on the left to make them easy to spot.
If we delete the hero name, the form displays a validation error in an attention grabbing style: If we delete the hero name, the form displays a validation error in an attention-grabbing style:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required") img(src="/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required")
:marked :marked
Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red. Note that the submit button is disabled, and the "required" bar to the left of the input control changed from green to red.
.l-sub-section .l-sub-section
p We'll customize the colors and location of the "required" bar with standard CSS. :marked
We'll customize the colors and location of the "required" bar with standard CSS.
:marked :marked
We will build this form in the following sequence of small steps We'll build this form in small steps:
1. Create the `Hero` model class 1. Create the `Hero` model class.
1. Create the component that controls the form 1. Create the component that controls the form.
1. Create a template with the initial form layout 1. Create a template with the initial form layout.
1. Bind data properties to each form input control with the `ngModel` two-way data binding syntax 1. Bind data properties to each form control using the `ngModel` two-way data binding syntax.
1. Add the `name` attribute to each form input control 1. Add a `name` attribute to each form input control.
1. Add custom CSS to provide visual feedback 1. Add custom CSS to provide visual feedback.
1. Show and hide validation error messages 1. Show and hide validation error messages.
1. Handle form submission with **ngSubmit** 1. Handle form submission with **ngSubmit**.
1. Disable the forms submit button until the form is valid 1. Disable the forms submit button until the form is valid.
:marked :marked
## Setup ## Setup
Follow the [setup](setup.html) instructions for creating a new project Follow the [setup](setup.html) instructions for creating a new project
named <span ngio-ex>angular-forms</span>. named <span ngio-ex>angular-forms</span>.
## Create the Hero Model Class ## Create the Hero model class
As users enter form data, we capture their changes and update an instance of a model. As users enter form data, we'll capture their changes and update an instance of a model.
We can't layout the form until we know what the model looks like. We can't lay out the form until we know what the model looks like.
A model can be as simple as a "property bag" that holds facts about a thing of application importance. A model can be as simple as a "property bag" that holds facts about a thing of application importance.
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`) That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
and one optional field (`alterEgo`). and one optional field (`alterEgo`).
Create a new file in the app folder called `hero.ts` and give it the following class definition: In the `!{_appDir}` directory, create the following file with the given content:
+makeExample('forms/ts/app/hero.ts', null, 'app/hero.ts') +makeExample('app/hero.ts')
:marked :marked
It's an anemic model with few requirements and no behavior. Perfect for our demo. It's an anemic model with few requirements and no behavior. Perfect for our demo.
@ -107,49 +105,40 @@ figure.image-display
The TypeScript compiler generates a public field for each `public` constructor parameter and The TypeScript compiler generates a public field for each `public` constructor parameter and
assigns the parameters value to that field automatically when we create new heroes. assigns the parameters value to that field automatically when we create new heroes.
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`. The `alterEgo` is optional, so the constructor lets us omit it; note the (?) in `alterEgo?`.
We can create a new hero like this: We can create a new hero like this:
code-example(format="").
let myHero = new Hero(42, 'SkyDog', +makeExcerpt('app/hero-form.component.ts', 'SkyDog', '')
'Fetch any object at any distance',
'Leslie Rollover');
console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
:marked
.l-main-section .l-main-section
:marked :marked
## Create a Form component ## Create a form component
An Angular form has two parts: an HTML-based _template_ and a component _class_ An Angular form has two parts: an HTML-based _template_ and a component _class_
to handle data and user interactions programmatically. to handle data and user interactions programmatically.
We begin with the class because it states, in brief, what the hero editor can do. We begin with the class because it states, in brief, what the hero editor can do.
Create a new file called `hero-form.component.ts` and give it the following definition: Create the following file with the given content:
+makeExample('forms/ts/app/hero-form.component.ts', 'first', 'app/hero-form.component.ts') +makeExcerpt('app/hero-form.component.ts', 'v1')
:marked :marked
Theres nothing special about this component, nothing form-specific, nothing to distinguish it from any component we've written before. Theres nothing special about this component, nothing form-specific,
nothing to distinguish it from any component we've written before.
Understanding this component requires only the Angular concepts weve learned in previous guides Understanding this component requires only the Angular concepts covered in previous guides.
1. We import the `Component` decorator from the Angular library as we usually do.
1. We import the `Hero` model we just created.
1. The code imports the Angular core library, and the `Hero` model we just created.
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag. 1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
1. The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`. 1. The `moduleId: module.id` property sets the base for module-relative loading of the `templateUrl`.
1. The `templateUrl` property points to a separate file for the template HTML.
1. The `templateUrl` property points to a separate file for the template HTML called `hero-form.component.html`. 1. We defined dummy data for `model` and `powers`, as befits a demo.
1. We defined dummy data for `model` and `powers` as befits a demo.
Down the road, we can inject a data service to get and save real data Down the road, we can inject a data service to get and save real data
or perhaps expose these properties as [inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a or perhaps expose these properties as
[inputs and outputs](./template-syntax.html#inputs-outputs) for binding to a
parent component. None of this concerns us now and these future changes won't affect our form. parent component. None of this concerns us now and these future changes won't affect our form.
1. We threw in a `diagnostic` property to return a JSON representation of our model.
1. We threw in a `diagnostic` property at the end to return a JSON representation of our model.
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later. It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
### Why the separate template file? ### Why the separate template file?
@ -168,7 +157,7 @@ code-example(format="").
.l-main-section .l-main-section
:marked :marked
## Revise the *app.module.ts* ## Revise *app.module.ts*
`app.module.ts` defines the application's root module. In it we identify the external modules we'll use in our application `app.module.ts` defines the application's root module. In it we identify the external modules we'll use in our application
and declare the components that belong to this module, such as our `HeroFormComponent`. and declare the components that belong to this module, such as our `HeroFormComponent`.
@ -199,27 +188,29 @@ code-example(format="").
.l-main-section .l-main-section
:marked :marked
## Revise the *app.component.ts* ## Revise *app.component.ts*
`app.component.ts` is the application's root component. It will host our new `HeroFormComponent`. `AppComponent` is the application's root component. It will host our new `HeroFormComponent`.
Replace the contents of the "QuickStart" version with the following: Replace the contents of the "QuickStart" version with the following:
+makeExample('forms/ts/app/app.component.ts', null, 'app/app.component.ts')
+makeExample('app/app.component.ts')
:marked :marked
.l-sub-section .l-sub-section
:marked :marked
There is only one change. There are only two changes.
The `template` is simply the new element tag identified by the component's `selector` property. The `template` is simply the new element tag identified by the component's `selector` property.
This will display the hero form when the application component is loaded. This will display the hero form when the application component is loaded.
We've also dropped the `name` field from the class body.
.l-main-section .l-main-section
:marked :marked
## Create an initial HTML Form Template ## Create an initial HTML form template
Create a new template file called `hero-form.component.html` and give it the following definition: Create the new template file with the following contents:
+makeExample('forms/ts/app/hero-form.component.html', 'start', 'app/hero-form.component.html') +makeExample('app/hero-form.component.html', 'start')
:marked :marked
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
@ -230,45 +221,47 @@ code-example(format="").
We've got a *Submit* button at the bottom with some classes on it for styling. We've got a *Submit* button at the bottom with some classes on it for styling.
**We are not using Angular yet**. There are no bindings. No extra directives. Just layout. **We are not using Angular yet**. There are no bindings, no extra directives, just layout.
The `container`, `form-group`, `form-control`, and `btn` classes The `container`, `form-group`, `form-control`, and `btn` classes
come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic. come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic.
We're using Bootstrap to give the form a little style! We're using Bootstrap to give the form a little style!
.callout.is-important .callout.is-important
header Angular Forms Do Not Require A Style Library header Angular forms do not require a style library
:marked :marked
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
the styles of any external library. Angular apps can use any CSS library the styles of any external library. Angular apps can use any CSS library, or none at all.
... or none at all.
:marked :marked
Let's add the stylesheet. Open <code>index.html</code> and add the following link to the <code>&lt;head></code>. Let's add the stylesheet. Open `index.html` and add the following link to the `<head>`:
+makeExample('forms/ts/index.html', 'bootstrap')(format=".")
+makeExcerpt('index.html', 'bootstrap')
:marked
.l-main-section .l-main-section
:marked :marked
## Add Powers with ***ngFor** ## Add powers with _*ngFor_
Our hero may choose one super power from a fixed list of Agency-approved powers.
Our hero must choose one super power from a fixed list of Agency-approved powers.
We maintain that list internally (in `HeroFormComponent`). We maintain that list internally (in `HeroFormComponent`).
We'll add a `select` to our We'll add a `select` to our
form and bind the options to the `powers` list using `ngFor`, form and bind the options to the `powers` list using `ngFor`,
a technique seen previously in the [Displaying Data](./displaying-data.html) guide. a technique seen previously in the [Displaying Data](./displaying-data.html) guide.
Add the following HTML *immediately below* the *Alter Ego* group. Add the following HTML *immediately below* the *Alter Ego* group:
+makeExample('forms/ts/app/hero-form.component.html', 'powers', 'app/hero-form.component.html (excerpt)')(format=".")
+makeExcerpt('app/hero-form.component.html (powers)')
:marked :marked
We are repeating the `<options>` tag for each power in the list of Powers. This code repeats the `<option>` tag for each power in the list of powers.
The `pow` template input variable is a different power in each iteration; The `pow` template input variable is a different power in each iteration;
we display its name using the interpolation syntax with the double-curly-braces. we display its name using the interpolation syntax.
.l-main-section#ngModel .l-main-section#ngModel
:marked :marked
## Two-way data binding with **_ngModel_** ## Two-way data binding with _ngModel_
Running the app right now would be disappointing. Running the app right now would be disappointing.
figure.image-display figure.image-display
@ -276,23 +269,23 @@ figure.image-display
:marked :marked
We don't see hero data because we are not binding to the `Hero` yet. We don't see hero data because we are not binding to the `Hero` yet.
We know how to do that from earlier guides. We know how to do that from earlier guides.
[Displaying Data](./displaying-data.html) taught us Property Binding. [Displaying Data](./displaying-data.html) taught us property binding.
[User Input](./user-input.html) showed us how to listen for DOM events with an [User Input](./user-input.html) showed us how to listen for DOM events with an
Event Binding and how to update a component property with the displayed value. event binding and how to update a component property with the displayed value.
Now we need to display, listen, and extract at the same time. Now we need to display, listen, and extract at the same time.
We could use those techniques again in our form. We could use the techniques we already know, but
Instead we'll introduce something new, the `[(ngModel)]` syntax, that instead we'll introduce something new: the `[(ngModel)]` syntax, which
makes binding our form to the model super-easy. makes binding the form to the model super easy.
Find the `<input>` tag for the "Name" and update it like this Find the `<input>` tag for *Name* and update it like this:
+makeExample('forms/ts/app/hero-form.component.html', 'ngModelName-1','app/hero-form.component.html (excerpt)')(format=".") +makeExcerpt('app/hero-form.component.html (excerpt)', 'ngModelName-1')
.l-sub-section .l-sub-section
:marked :marked
We appended a diagnostic interpolation after the input tag We added a diagnostic interpolation after the input tag
so we can see what we're doing. so we can see what we're doing.
We left ourselves a note to throw it away when we're done. We left ourselves a note to throw it away when we're done.
@ -303,103 +296,71 @@ figure.image-display
adding and deleting characters, we'd see them appearing and disappearing adding and deleting characters, we'd see them appearing and disappearing
from the interpolated text. from the interpolated text.
At some point it might look like this. At some point it might look like this.
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action") img(src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ngModel in action")
:marked
The diagnostic is evidence that we really are flowing values from the input box to the model and
back again. **That's two-way data binding!**
:marked
The diagnostic is evidence that values really are flowing from the input box to the model and
back again.
.l-sub-section
:marked
That's **two-way data binding**!
For more information about `[(ngModel)]` and two-way data bindings, see
the [Template Syntax](template-syntax.html#ngModel) page.
:marked
Notice that we also added a `name` attribute to our `<input>` tag and set it to "name" Notice that we also added a `name` attribute to our `<input>` tag and set it to "name"
which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful. which makes sense for the hero's name. Any unique value will do, but using a descriptive name is helpful.
Defining a `name` attribute is a requirement when using `[(ngModel)]` in combination with a form. Defining a `name` attribute is a requirement when using `[(ngModel)]` in combination with a form.
.l-sub-section .l-sub-section
:marked :marked
Internally Angular creates `FormControls` and registers them with an `NgForm` directive that Angular Internally Angular creates `FormControl` instances and
attached to the `<form>` tag. Each `FormControl` is registered under the name we assigned to the `name` attribute. registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
Each `FormControl` is registered under the name we assigned to the `name` attribute.
We'll talk about `NgForm` [later in this guide](#ngForm). We'll talk about `NgForm` [later in this guide](#ngForm).
:marked :marked
Let's add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*. Let's add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
We'll ditch the input box binding message We'll ditch the input box binding message
and add a new binding at the top to the component's `diagnostic` property. and add a new binding (at the top) to the component's `diagnostic` property.
Then we can confirm that two-way data binding works *for the entire hero model*. Then we can confirm that two-way data binding works *for the entire hero model*.
After revision the core of our form should have three `[(ngModel)]` bindings and `name` attributes that After revision, the core of our form should look like this:
look much like this:
+makeExample('forms/ts/app/hero-form.component.html', 'ngModel-2', 'app/hero-form.component.html (excerpt)') +makeExcerpt('app/hero-form.component.html (excerpt)', 'ngModel-2')
.l-sub-section .l-sub-section
:marked :marked
- Each input element has an `id` property that is used by the `label` element's `for` attribute - Each input element has an `id` property that is used by the `label` element's `for` attribute
to match the label to its input control. to match the label to its input control.
- Each input element has a `name` property that is required by Angular Forms to register the control with the form. - Each input element has a `name` property that is required by Angular forms to register the control with the form.
:marked :marked
If we run the app right now and changed every hero model property, the form might display like this: If we run the app now and changed every hero model property, the form might display like this:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in super action") img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ngModel in action")
:marked :marked
The diagnostic near the top of the form The diagnostic near the top of the form
confirms that all of our changes are reflected in the model. confirms that all of our changes are reflected in the model.
**Delete** the `{{diagnostic}}` binding at the top as it has served its purpose. **Delete** the `{{diagnostic}}` binding at the top as it has served its purpose.
.l-sub-section
:marked
### Inside _[(ngModel)]_
*This section is an optional deep dive into [(ngModel)]. Not interested? Skip ahead!*
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on.
In a Property Binding, a value flows from the model to a target property on screen.
We identify that target property by surrounding its name in brackets, <span style="font-family:courier"><b>[]</b></span>.
This is a one-way data binding **from the model to the view**.
In an Event Binding, we flow the value from the target property on screen to the model.
We identify that target property by surrounding its name in parentheses, <span style="font-family:courier"><b>()</b></span>.
This is a one-way data binding in the opposite direction **from the view to the model**.
No wonder Angular chose to combine the punctuation as <span style="font-family:courier"><b>[()]</b></span>
to signify a two-way data binding and a **flow of data in both directions**.
In fact, we can break the `NgModel` binding into its two separate modes
as we do in this re-write of the "Name" `<input>` binding:
+makeExample('forms/ts/app/hero-form.component.html', 'ngModel-3','app/hero-form.component.html (excerpt)')(format=".")
:marked
<br>The Property Binding should feel familiar. The Event Binding might seem strange.
The `ngModelChange` is not an `<input>` element event.
It is actually an event property of the `NgModel` directive.
When Angular sees a binding target in the form <span style="font-family:courier">[(x)]</span>,
it expects the `x` directive to have an `x` input property and an `xChange` output property.
The other oddity is the template expression, `model.name = $event`.
We're used to seeing an `$event` object coming from a DOM event.
The `ngModelChange` property doesn't produce a DOM event; it's an Angular `EventEmitter`
property that returns the input box value when it fires &mdash; which is precisely what
we should assign to the model's `name` property.
Nice to know but is it practical? We almost always prefer `[(ngModel)]`.
We might split the binding if we had to do something special in
the event handling such as debounce or throttle the key strokes.
Learn more about `NgModel` and other template syntax in the
[Template Syntax](./template-syntax.html) guide.
.l-main-section .l-main-section
:marked :marked
## Track change-state and validity with **_ngModel_** ## Track control state and validity with _ngModel_
A form isn't just about data binding. We'd also like to know the state of the controls on our form. A form isn't just about data binding. We'd also like to know the state of the controls in our form.
Using `ngModel` in a form gives us more than just two way data binding. It also tells us if the user touched the control, if the value changed, or if the value became invalid. Using `ngModel` in a form gives us more than just a two way data binding. It also tells
us if the user touched the control, if the value changed, or if the value became invalid.
The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state. The *NgModel* directive doesn't just track state; it updates the control with special Angular CSS classes that reflect the state.
We can leverage those class names to change the appearance of the We can leverage those class names to change the appearance of the control.
control and make messages appear or disappear.
table table
tr tr
@ -418,65 +379,70 @@ table
td Control's value is valid td Control's value is valid
td <code>ng-valid</code> td <code>ng-valid</code>
td <code>ng-invalid</code> td <code>ng-invalid</code>
:marked
Let's add a temporary [template reference variable](./template-syntax.html#ref-vars) named **spy**
to the "Name" `<input>` tag and use the spy to display those classes.
+makeExample('forms/ts/app/hero-form.component.html', 'ngModelName-2','app/hero-form.component.html (excerpt)')(format=".")
:marked :marked
Now run the app and focus on the *Name* input box. Let's temporarily add a [template reference variable](./template-syntax.html#ref-vars) named `spy`
Follow the next four steps *precisely* to the _Name_ `<input>` tag and use it to display the input's CSS classes.
1. Look but don't touch +makeExcerpt('app/hero-form.component.html (excerpt)', 'ngModelName-2')
1. Click in the input box, then click outside the text input box
1. Add slashes to the end of the name :marked
1. Erase the name Now run the app, and look at the _Name_ input box.
Follow the next four steps *precisely*:
1. Look but don't touch.
1. Click inside the name box, then click outside it.
1. Add slashes to the end of the name.
1. Erase the name.
The actions and effects are as follows: The actions and effects are as follows:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition") img(src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition")
:marked :marked
We should be able to see the following four sets of class names and their transitions: We should see the following transitions and class names:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" width="400px" alt="Control State Transitions") img(src="/resources/images/devguide/forms/ng-control-class-changes.png" width="500px" alt="Control state transitions")
:marked :marked
The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a The `ng-valid`/`ng-invalid` pair is the most interesting to us, because we want to send a
strong visual signal when the data are invalid and we want to mark required fields. strong visual signal when the values are invalid. We also want to mark required fields.
So we add custom CSS for visual feedback. To create such visual feedback, let's add definitions for the `ng-*` CSS classes.
**Delete** the `#spy` template reference variable and `TODO` as they have served their purpose. **Delete** the `#spy` template reference variable and the `TODO` as they have served their purpose.
.l-main-section .l-main-section
:marked :marked
## Add Custom CSS for Visual Feedback ## Add custom CSS for visual feedback
We realize we can mark required fields and invalid data at the same time with a colored bar
We can mark required fields and invalid data at the same time with a colored bar
on the left of the input box: on the left of the input box:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form") img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form")
:marked :marked
We achieve this effect by adding two styles to a new `forms.css` file We achieve this effect by adding these class definitions to a new `forms.css` file
that we add to our project as a sibling to `index.html`. that we add to our project as a sibling to `index.html`:
+makeExample('forms.css')
+makeExample('forms/ts/forms.css',null,'forms.css')(format=".")
:marked :marked
These styles select for the two Angular validity classes and the HTML 5 "required" attribute. Update the `<head>` of `index.html` to include this style sheet:
+makeExcerpt('index.html', 'styles')
We update the `<head>` of the `index.html` to include this style sheet.
+makeExample('forms/ts/index.html', 'styles', 'index.html (excerpt)')(format=".")
:marked :marked
## Show and Hide Validation Error messages ## Show and hide validation error messages
We can do better. We can do better. The _Name_ input box is required and clearing it turns the bar red.
That says *something* is wrong but we don't know *what* is wrong or what to do about it.
The "Name" input box is required. Clearing it turns the bar red. That says *something* is wrong but we We can leverage the control's state to reveal a helpful message.
don't know *what* is wrong or what to do about it.
We can leverage the `ng-invalid` class to reveal a helpful message.
Here's the way it should look when the user deletes the name: Here's the way it should look when the user deletes the name:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required") img(src="/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required")
@ -485,13 +451,14 @@ figure.image-display
1. a [template reference variable](./template-syntax.html#ref-vars) 1. a [template reference variable](./template-syntax.html#ref-vars)
1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid. 1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
Here's how we do it for the *name* input box: Here's an example of adding an error message to the _name_ input box:
+makeExample('forms/ts/app/hero-form.component.html',
'name-with-error-msg', +makeExcerpt('app/hero-form.component.html (excerpt)', 'name-with-error-msg')
'app/hero-form.component.html (excerpt)')(format=".")
:marked :marked
We need a template reference variable to access the input box's Angular control from within the template. We need a template reference variable to access the input box's Angular control from within the template.
Here we created a variable called `name` and gave it the value "ngModel". Here we created a variable called `name` and gave it the value "ngModel".
.l-sub-section .l-sub-section
:marked :marked
Why "ngModel"? Why "ngModel"?
@ -499,10 +466,12 @@ figure.image-display
tells Angular how to link the reference variable to the directive. tells Angular how to link the reference variable to the directive.
We set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel". We set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
Now we can control visibility of the "name" error message by binding properties of the `name` control to the message `<div>` element's `hidden` property. :marked
+makeExample('forms/ts/app/hero-form.component.html', We control visibility of the name error message by binding properties of the `name`
'hidden-error-msg', control to the message `<div>` element's `hidden` property.
'app/hero-form.component.html (excerpt)')(format='.')
+makeExcerpt('app/hero-form.component.html', 'hidden-error-msg', '')
:marked :marked
In this example, we hide the message when the control is valid or pristine; In this example, we hide the message when the control is valid or pristine;
pristine means the user hasn't changed the value since it was displayed in this form. pristine means the user hasn't changed the value since it was displayed in this form.
@ -512,31 +481,29 @@ figure.image-display
If we arrive in this component with a new (blank) hero or an invalid hero, If we arrive in this component with a new (blank) hero or an invalid hero,
we'll see the error message immediately, before we've done anything. we'll see the error message immediately, before we've done anything.
Some folks find that behavior disconcerting. They only want to see the message when the user makes an invalid change. Some folks find that behavior disconcerting.
They only want to see the message when the user makes an invalid change.
Hiding the message while the control is "pristine" achieves that goal. Hiding the message while the control is "pristine" achieves that goal.
We'll see the significance of this choice when we [add a new hero](#new-hero) to the form. We'll see the significance of this choice when we [add a new hero](#new-hero) to the form.
The hero *Alter Ego* is optional so we can leave that be. The hero *Alter Ego* is optional so we can leave that be.
Hero *Power* selection is required. Hero *Power* selection is required.
We can add the same kind of error handling to the `<select>` if we want We can add the same kind of error handling to the `<select>` if we want,
but it's not imperative because the selection box already constrains the but it's not imperative because the selection box already constrains the
power to valid value. power to valid values.
<a id="new-hero"></a>
<a id="reset"></a>
.l-main-section
:marked
## Add a hero and reset the form
We'd like to add a new hero in this form. We'd like to add a new hero in this form.
We place a "New Hero" button at the bottom of the form and bind its click event to a `newHero` component method. We place a "New Hero" button at the bottom of the form and bind its click event to a `newHero` component method.
+makeExample('forms/ts/app/hero-form.component.html', +makeExample('forms/ts/app/hero-form.component.html',
'new-hero-button-no-reset', 'new-hero-button-no-reset',
'app/hero-form.component.html (New Hero button)') 'app/hero-form.component.html (New Hero button)')
:marked
+makeExample('forms/ts/app/hero-form.component.ts', +makeExample('forms/ts/app/hero-form.component.ts',
'new-hero', 'new-hero',
'app/hero-form.component.ts (New Hero method)')(format=".") 'app/hero-form.component.ts (New Hero method)')(format=".")
:marked :marked
Run the application again, click the *New Hero* button, and the form clears. Run the application again, click the *New Hero* button, and the form clears.
The *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties. The *required* bars to the left of the input box are red, indicating invalid `name` and `power` properties.
@ -554,74 +521,87 @@ figure.image-display
We have to clear all of the flags imperatively which we can do We have to clear all of the flags imperatively which we can do
by calling the form's `reset()` method after calling the `newHero()` method. by calling the form's `reset()` method after calling the `newHero()` method.
+makeExample('forms/ts/app/hero-form.component.html', +makeExample('forms/ts/app/hero-form.component.html',
'new-hero-button-form-reset', 'new-hero-button-form-reset',
'app/hero-form.component.html (Reset the form)') 'app/hero-form.component.html (Reset the form)')
:marked :marked
Now clicking "New Hero" both resets the form and its control flags. Now clicking "New Hero" both resets the form and its control flags.
:marked
.l-main-section .l-main-section
:marked :marked
## Submit the form with **_ngSubmit_** ## Submit the form with _ngSubmit_
The user should be able to submit this form after filling it in. The user should be able to submit this form after filling it in.
The Submit button at the bottom of the form The Submit button at the bottom of the form
does nothing on its own but it will does nothing on its own, but it will
trigger a form submit because of its type (`type="submit"`). trigger a form submit because of its type (`type="submit"`).
A "form submit" is useless at the moment. A "form submit" is useless at the moment.
To make it useful, bind the `NgForm` directive's `ngSubmit` event property (in the `<form>` tag) To make it useful, bind the form's `ngSubmit` event property
to the `HeroFormComponent.submit()` method: to the hero form component's `onSubmit()` method:
+makeExample('forms/ts/app/hero-form.component.html', 'ngSubmit')(format=".")
+makeExcerpt('forms/ts/app/hero-form.component.html (ngSubmit)')
:marked :marked
We slipped in something extra there at the end! We defined a We slipped in something extra there at the end! We defined a
template reference variable, **`#heroForm`**, and initialized it with the value, "ngForm". template reference variable, **`#heroForm`**, and initialized it with the value "ngForm".
The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole. The variable `heroForm` is now a reference to the `NgForm` directive that governs the form as a whole.
<a id="ngForm"></a>
.l-sub-section .l-sub-section#ngForm
:marked :marked
### The _NgForm_ directive ### The _NgForm_ directive
What `NgForm` directive? We didn't add an [NgForm](../api/forms/index/NgForm-directive.html) directive!
What `NgForm` directive?
We didn't add an [NgForm](../api/forms/index/NgForm-directive.html) directive!
Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically. Angular did. Angular creates and attaches an `NgForm` directive to the `<form>` tag automatically.
The `NgForm` directive supplements the `form` element with additional features. The `NgForm` directive supplements the `form` element with additional features.
It holds the controls we created for the elements with `ngModel` directive and `name` attribute It holds the controls we created for the elements with an `ngModel` directive
and monitors their properties including their validity. and `name` attribute, and monitors their properties including their validity.
It also has its own `valid` property which is true only *if every contained It also has its own `valid` property which is true only *if every contained
control* is valid. control* is valid.
:marked :marked
Later in the template we bind the button's `disabled` property to the form's over-all validity via We'll bind the form's overall validity via
the `heroForm` variable. Here's that bit of markup: the `heroForm` variable to the button's `disabled` property
+makeExample('forms/ts/app/hero-form.component.html', 'submit-button') using an event binding. Here's the code:
:marked
Re-run the application. The form opens in a valid state and the button is enabled.
Now delete the *Name*. We violate the "name required" rule which +makeExcerpt('app/hero-form.component.html', 'submit-button', '')
is duly noted in our error message as before. And now the Submit button is also disabled.
:marked
If we run the application now, we find that the button is enabled
&mdash; although it doesn't do anything useful yet.
Now if we delete the Name, we violate the "required" rule, which
is duly noted in the error message.
The Submit button is also disabled.
Not impressed? Think about it for a moment. What would we have to do to Not impressed? Think about it for a moment. What would we have to do to
wire the button's enable/disabled state to the form's validity without Angular's help? wire the button's enable/disabled state to the form's validity without Angular's help?
For us, it was as simple as For us, it was as simple as:
1. Define a template reference variable on the (enhanced) form element
2. Reference that variable in a button some 50 lines away. 1. Define a template reference variable on the (enhanced) form element.
2. Refer to that variable in a button many lines away.
.l-main-section .l-main-section
:marked :marked
## Toggle two form regions (extra credit) ## Toggle two form regions (extra credit)
Submitting the form isn't terribly dramatic at the moment. Submitting the form isn't terribly dramatic at the moment.
.l-sub-section .l-sub-section
:marked :marked
An unsurprising observation for a demo. To be honest, An unsurprising observation for a demo. To be honest,
jazzing it up won't teach us anything new about forms. jazzing it up won't teach us anything new about forms.
But this is an opportunity to exercise some of our newly won But this is an opportunity to exercise some of our newly won
binding skills. binding skills.
If you're not interested, you can skip to the guide's conclusion If you aren't interested, go ahead and skip to this guide's conclusion.
and not miss a thing.
:marked :marked
Let's do something more strikingly visual. Let's do something more strikingly visual.
Let's hide the data entry area and display something else. Let's hide the data entry area and display something else.
@ -629,31 +609,32 @@ figure.image-display
Start by wrapping the form in a `<div>` and bind Start by wrapping the form in a `<div>` and bind
its `hidden` property to the `HeroFormComponent.submitted` property. its `hidden` property to the `HeroFormComponent.submitted` property.
+makeExample('forms/ts/app/hero-form.component.html', 'edit-div', 'app/hero-form.component.html (excerpt)')(format=".") +makeExcerpt('app/hero-form.component.html (excerpt)', 'edit-div')
:marked :marked
The main form is visible from the start because the The main form is visible from the start because the
the `submitted` property is false until we submit the form, `submitted` property is false until we submit the form,
as this fragment from the `HeroFormComponent` reminds us: as this fragment from the `HeroFormComponent` shows:
+makeExample('forms/ts/app/hero-form.component.ts', 'submitted')(format=".") +makeExcerpt('app/hero-form.component.ts', 'submitted')
:marked :marked
When we click the Submit button, the `submitted` flag becomes true and the form disappears When we click the Submit button, the `submitted` flag becomes true and the form disappears
as planned. as planned.
Now we need to show something else while the form is in the submitted state. Now the app needs to show something else while the form is in the submitted state.
Add the following block of HTML below the `<div>` wrapper we just wrote: Add the following HTML below the `<div>` wrapper we just wrote:
+makeExample('forms/ts/app/hero-form.component.html', 'submitted', 'app/hero-form.component.html (excerpt)')
+makeExcerpt('app/hero-form.component.html (excerpt)', 'submitted')
:marked :marked
There's our hero again, displayed read-only with interpolation bindings. There's our hero again, displayed read-only with interpolation bindings.
This slug of HTML only appears while the component is in the submitted state. This `<div>` appears only while the component is in the submitted state.
We added an Edit button whose click event is bound to an expression The HTML includes an _Edit_ button whose click event is bound to an expression
that clears the `submitted` flag. that clears the `submitted` flag.
When we click it, this block disappears and the editable form reappears. When we click the _Edit_ button, this block disappears and the editable form reappears.
That's as much drama as we can muster for now. That's as much drama as we can muster for now.
@ -661,19 +642,21 @@ figure.image-display
:marked :marked
## Conclusion ## Conclusion
The Angular form techniques discussed in this guide take The Angular form discussed in this guide takes advantage of the following
advantage of the following framework features to provide support for data modification, validation and more: framework features to provide support for data modification, validation, and more:
- An Angular HTML form template. - An Angular HTML form template.
- A form component class with a `Component` decorator. - A form component class with a `@Component` decorator.
- Handling form submission by binding to the `NgForm.ngSubmit` event property. - Handling form submission by binding to the `NgForm.ngSubmit` event property.
- Template reference variables such as `#heroForm`, `#name` and `#power`. - Template reference variables such as `#heroForm` and `#name`.
- The `[(ngModel)]` syntax and a `name` attribute for two-way data binding, validation and change tracking. - `[(ngModel)]` syntax for two-way data binding.
- The use of `name` attributes for validation and form element change tracking.
- The reference variables `valid` property on input controls to check if a control is valid and show/hide error messages. - The reference variables `valid` property on input controls to check if a control is valid and show/hide error messages.
- Controlling the submit button's enabled state by binding to `NgForm` validity. - Controlling the submit button's enabled state by binding to `NgForm` validity.
- Custom CSS classes that provide visual feedback to users about invalid controls. - Custom CSS classes that provide visual feedback to users about invalid controls.
Our final project folder structure should look like this: Our final project folder structure should look like this:
.filetree .filetree
.file angular-forms .file angular-forms
.children .children
@ -690,7 +673,7 @@ figure.image-display
.file package.json .file package.json
.file tsconfig.json .file tsconfig.json
:marked :marked
Heres the final version of the source: Heres the code for the final version of the application:
+makeTabs( +makeTabs(
`forms/ts/app/hero-form.component.ts, `forms/ts/app/hero-form.component.ts,
@ -710,4 +693,3 @@ figure.image-display
main.ts, main.ts,
index.html, index.html,
forms.css`) forms.css`)
:marked