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

View File

@ -1,6 +1,5 @@
// #docplaster
// #docregion
// #docregion first, final
// #docregion , v1, final
import { Component } from '@angular/core';
import { Hero } from './hero';
@ -26,21 +25,29 @@ export class HeroFormComponent {
// #enddocregion final
// TODO: Remove this when we're done
get diagnostic() { return JSON.stringify(this.model); }
// #enddocregion first
// #enddocregion v1
// #docregion final
// #docregion new-hero
// #docregion final, new-hero
newHero() {
this.model = new Hero(42, '', '');
}
// #enddocregion new-hero
// #enddocregion final
// #enddocregion final, new-hero
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 ////////
// Reveal in html:
// Name via form.controls = {{showFormControls(heroForm)}}
showFormControls(form: any) {
return form && form.controls['name'] &&
// #docregion form-controls
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
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.
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**
... 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
- two-way data bind with `[(ngModel)]` syntax for reading and writing values to input controls
- track the change state and validity of form controls using `ngModel` in combination with a form
- 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
- Build an Angular form with a component and template
- Use `ngModel` to create two-way data bindings for reading and writing input control values
- Track state changes and the validity of form controls
- Provide visual feedback using special CSS classes that track the state of the controls
- Display validation errors to users and enable/disable form controls
- Share information across HTML elements using template reference variables
Run the <live-example></live-example>.
.l-main-section
: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
the form-specific directives and techniques described in this guide.
.l-sub-section
:marked
That's not the only way to create a form but it's the way we'll cover in this guide.
: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,
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.
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
img(src="/resources/images/devguide/forms/hero-form-1.png" width="400px" alt="Clean Form")
:marked
Here at the *Hero Employment Agency* we use this form to maintain personal information about the
heroes in our stable. Every hero needs a job. It's our company mission to match the right hero with the right crisis!
Here at the *Hero Employment Agency* we use this form to maintain personal information about heroes.
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.
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
img(src="/resources/images/devguide/forms/hero-form-2.png" width="400px" alt="Invalid, Name Required")
: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
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
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 component that controls the form
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. Add the `name` attribute to each form input control
1. Add custom CSS to provide visual feedback
1. Show and hide validation error messages
1. Handle form submission with **ngSubmit**
1. Disable the forms submit button until the form is valid
1. Create the `Hero` model class.
1. Create the component that controls the form.
1. Create a template with the initial form layout.
1. Bind data properties to each form control using the `ngModel` two-way data binding syntax.
1. Add a `name` attribute to each form input control.
1. Add custom CSS to provide visual feedback.
1. Show and hide validation error messages.
1. Handle form submission with **ngSubmit**.
1. Disable the forms submit button until the form is valid.
:marked
## Setup
Follow the [setup](setup.html) instructions for creating a new project
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.
We can't layout the form until we know what the model looks like.
As users enter form data, we'll capture their changes and update an instance of a model.
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.
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
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
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
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:
code-example(format="").
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"
:marked
+makeExcerpt('app/hero-form.component.ts', 'SkyDog', '')
.l-main-section
:marked
## Create a Form component
## Create a form component
An Angular form has two parts: an HTML-based _template_ and a component _class_
to handle data and user interactions programmatically.
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
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
1. We import the `Component` decorator from the Angular library as we usually do.
1. We import the `Hero` model we just created.
Understanding this component requires only the Angular concepts covered in previous guides.
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 `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 called `hero-form.component.html`.
1. We defined dummy data for `model` and `powers` as befits a demo.
1. The `templateUrl` property points to a separate file for the template HTML.
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
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.
1. We threw in a `diagnostic` property at the end to return a JSON representation of our model.
1. We threw in a `diagnostic` property 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.
### Why the separate template file?
@ -168,7 +157,7 @@ code-example(format="").
.l-main-section
: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
and declare the components that belong to this module, such as our `HeroFormComponent`.
@ -199,27 +188,29 @@ code-example(format="").
.l-main-section
: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:
+makeExample('forms/ts/app/app.component.ts', null, 'app/app.component.ts')
+makeExample('app/app.component.ts')
:marked
.l-sub-section
: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.
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
: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
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 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
come from [Twitter Bootstrap](http://getbootstrap.com/css/). Purely cosmetic.
We're using Bootstrap to give the form a little style!
.callout.is-important
header Angular Forms Do Not Require A Style Library
header Angular forms do not require a style library
:marked
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
... or none at all.
the styles of any external library. Angular apps can use any CSS library, or none at all.
:marked
Let's add the stylesheet. Open <code>index.html</code> and add the following link to the <code>&lt;head></code>.
+makeExample('forms/ts/index.html', 'bootstrap')(format=".")
Let's add the stylesheet. Open `index.html` and add the following link to the `<head>`:
+makeExcerpt('index.html', 'bootstrap')
:marked
.l-main-section
:marked
## Add Powers with ***ngFor**
Our hero may choose one super power from a fixed list of Agency-approved powers.
## Add powers with _*ngFor_
Our hero must choose one super power from a fixed list of Agency-approved powers.
We maintain that list internally (in `HeroFormComponent`).
We'll add a `select` to our
form and bind the options to the `powers` list using `ngFor`,
a technique seen previously in the [Displaying Data](./displaying-data.html) guide.
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=".")
Add the following HTML *immediately below* the *Alter Ego* group:
+makeExcerpt('app/hero-form.component.html (powers)')
: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;
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
:marked
## Two-way data binding with **_ngModel_**
## Two-way data binding with _ngModel_
Running the app right now would be disappointing.
figure.image-display
@ -276,23 +269,23 @@ figure.image-display
:marked
We don't see hero data because we are not binding to the `Hero` yet.
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
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.
We could use those techniques again in our form.
Instead we'll introduce something new, the `[(ngModel)]` syntax, that
makes binding our form to the model super-easy.
We could use the techniques we already know, but
instead we'll introduce something new: the `[(ngModel)]` syntax, which
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
: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.
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
from the interpolated text.
At some point it might look like this.
figure.image-display
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"
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.
.l-sub-section
:marked
Internally Angular creates `FormControls` and 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.
Internally Angular creates `FormControl` instances and
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).
:marked
Let's add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
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*.
After revision the core of our form should have three `[(ngModel)]` bindings and `name` attributes that
look much like this:
After revision, the core of our form should look 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
:marked
- 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.
- 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
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
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
The diagnostic near the top of the form
confirms that all of our changes are reflected in the model.
**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
: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.
We can leverage those class names to change the appearance of the
control and make messages appear or disappear.
We can leverage those class names to change the appearance of the control.
table
tr
@ -418,65 +379,70 @@ table
td Control's value is valid
td <code>ng-valid</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
Now run the app and focus on the *Name* input box.
Follow the next four steps *precisely*
Let's temporarily add a [template reference variable](./template-syntax.html#ref-vars) named `spy`
to the _Name_ `<input>` tag and use it to display the input's CSS classes.
1. Look but don't touch
1. Click in the input box, then click outside the text input box
1. Add slashes to the end of the name
1. Erase the name
+makeExcerpt('app/hero-form.component.html (excerpt)', 'ngModelName-2')
:marked
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:
figure.image-display
img(src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition")
: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
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
The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a
strong visual signal when the data are invalid and we want to mark required fields.
So we add custom CSS for visual feedback.
The `ng-valid`/`ng-invalid` pair is the most interesting to us, because we want to send a
strong visual signal when the values are invalid. We also want to mark required fields.
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
:marked
## Add Custom CSS for Visual Feedback
We realize we can mark required fields and invalid data at the same time with a colored bar
## Add custom CSS for visual feedback
We can mark required fields and invalid data at the same time with a colored bar
on the left of the input box:
figure.image-display
img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form")
:marked
We achieve this effect by adding two styles to a new `forms.css` file
that we add to our project as a sibling to `index.html`.
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`:
+makeExample('forms.css')
+makeExample('forms/ts/forms.css',null,'forms.css')(format=".")
: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
## Show and Hide Validation Error messages
## Show and hide validation error messages
We can do better.
The "Name" input box is required. 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.
We can leverage the `ng-invalid` class to reveal a helpful message.
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.
We can leverage the control's state to reveal a helpful message.
Here's the way it should look when the user deletes the name:
figure.image-display
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. 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:
+makeExample('forms/ts/app/hero-form.component.html',
'name-with-error-msg',
'app/hero-form.component.html (excerpt)')(format=".")
Here's an example of adding an error message to the _name_ input box:
+makeExcerpt('app/hero-form.component.html (excerpt)', 'name-with-error-msg')
:marked
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".
.l-sub-section
:marked
Why "ngModel"?
@ -499,10 +466,12 @@ figure.image-display
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".
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.
+makeExample('forms/ts/app/hero-form.component.html',
'hidden-error-msg',
'app/hero-form.component.html (excerpt)')(format='.')
:marked
We control visibility of the name error message by binding properties of the `name`
control to the message `<div>` element's `hidden` property.
+makeExcerpt('app/hero-form.component.html', 'hidden-error-msg', '')
:marked
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.
@ -512,31 +481,29 @@ figure.image-display
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.
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.
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.
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
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 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',
'new-hero-button-no-reset',
'app/hero-form.component.html (New Hero button)')
:marked
+makeExample('forms/ts/app/hero-form.component.ts',
'new-hero',
'app/hero-form.component.ts (New Hero method)')(format=".")
:marked
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.
@ -554,74 +521,87 @@ figure.image-display
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.
+makeExample('forms/ts/app/hero-form.component.html',
'new-hero-button-form-reset',
'app/hero-form.component.html (Reset the form)')
:marked
Now clicking "New Hero" both resets the form and its control flags.
:marked
.l-main-section
: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 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"`).
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 the `HeroFormComponent.submit()` method:
+makeExample('forms/ts/app/hero-form.component.html', 'ngSubmit')(format=".")
To make it useful, bind the form's `ngSubmit` event property
to the hero form component's `onSubmit()` method:
+makeExcerpt('forms/ts/app/hero-form.component.html (ngSubmit)')
:marked
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.
<a id="ngForm"></a>
.l-sub-section
.l-sub-section#ngForm
:marked
### 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.
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
and monitors their properties including their validity.
It holds the controls we created for the elements with an `ngModel` directive
and `name` attribute, and monitors their properties including their validity.
It also has its own `valid` property which is true only *if every contained
control* is valid.
:marked
Later in the template we bind the button's `disabled` property to the form's over-all validity via
the `heroForm` variable. Here's that bit of markup:
+makeExample('forms/ts/app/hero-form.component.html', 'submit-button')
:marked
Re-run the application. The form opens in a valid state and the button is enabled.
We'll bind the form's overall validity via
the `heroForm` variable to the button's `disabled` property
using an event binding. Here's the code:
Now delete the *Name*. We violate the "name required" rule which
is duly noted in our error message as before. And now the Submit button is also disabled.
+makeExcerpt('app/hero-form.component.html', 'submit-button', '')
: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
wire the button's enable/disabled state to the form's validity without Angular's help?
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.
For us, it was as simple as:
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
:marked
## Toggle two form regions (extra credit)
Submitting the form isn't terribly dramatic at the moment.
.l-sub-section
:marked
An unsurprising observation for a demo. To be honest,
jazzing it up won't teach us anything new about forms.
But this is an opportunity to exercise some of our newly won
binding skills.
If you're not interested, you can skip to the guide's conclusion
and not miss a thing.
If you aren't interested, go ahead and skip to this guide's conclusion.
:marked
Let's do something more strikingly visual.
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
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
The main form is visible from the start because the
the `submitted` property is false until we submit the form,
as this fragment from the `HeroFormComponent` reminds us:
`submitted` property is false until we submit the form,
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
When we click the Submit button, the `submitted` flag becomes true and the form disappears
as planned.
Now we need 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:
+makeExample('forms/ts/app/hero-form.component.html', 'submitted', 'app/hero-form.component.html (excerpt)')
Now the app needs to show something else while the form is in the submitted state.
Add the following HTML below the `<div>` wrapper we just wrote:
+makeExcerpt('app/hero-form.component.html (excerpt)', 'submitted')
:marked
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.
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.
@ -661,19 +642,21 @@ figure.image-display
:marked
## Conclusion
The Angular form techniques discussed in this guide take
advantage of the following framework features to provide support for data modification, validation and more:
The Angular form discussed in this guide takes advantage of the following
framework features to provide support for data modification, validation, and more:
- 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.
- Template reference variables such as `#heroForm`, `#name` and `#power`.
- The `[(ngModel)]` syntax and a `name` attribute for two-way data binding, validation and change tracking.
- Template reference variables such as `#heroForm` and `#name`.
- `[(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.
- Controlling the submit button's enabled state by binding to `NgForm` validity.
- Custom CSS classes that provide visual feedback to users about invalid controls.
Our final project folder structure should look like this:
.filetree
.file angular-forms
.children
@ -690,7 +673,7 @@ figure.image-display
.file package.json
.file tsconfig.json
:marked
Heres the final version of the source:
Heres the code for the final version of the application:
+makeTabs(
`forms/ts/app/hero-form.component.ts,
@ -710,4 +693,3 @@ figure.image-display
main.ts,
index.html,
forms.css`)
:marked