docs(aio): update template forms to CLI (#20014)

PR Close #20014
This commit is contained in:
Jesus Rodriguez 2017-10-30 13:04:34 +01:00 committed by Jason Aden
parent 6ca780178c
commit 5a2531ee45
9 changed files with 115 additions and 378 deletions

View File

@ -0,0 +1 @@
<app-hero-form></app-hero-form>

View File

@ -3,6 +3,7 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
template: '<app-hero-form></app-hero-form>' templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
}) })
export class AppComponent { } export class AppComponent { }

View File

@ -4,7 +4,7 @@ import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms'; import { FormsModule } from '@angular/forms';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { HeroFormComponent } from './hero-form.component'; import { HeroFormComponent } from './hero-form/hero-form.component';
@NgModule({ @NgModule({
imports: [ imports: [
@ -15,6 +15,7 @@ import { HeroFormComponent } from './hero-form.component';
AppComponent, AppComponent,
HeroFormComponent HeroFormComponent
], ],
providers: [],
bootstrap: [ AppComponent ] bootstrap: [ AppComponent ]
}) })
export class AppModule { } export class AppModule { }

View File

@ -2,11 +2,12 @@
// #docregion , v1, final // #docregion , v1, final
import { Component } from '@angular/core'; import { Component } from '@angular/core';
import { Hero } from './hero'; import { Hero } from '../hero';
@Component({ @Component({
selector: 'app-hero-form', selector: 'app-hero-form',
templateUrl: './hero-form.component.html' templateUrl: './hero-form.component.html',
styleUrls: ['./hero-form.component.css']
}) })
export class HeroFormComponent { export class HeroFormComponent {

View File

@ -0,0 +1 @@
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');

View File

@ -29,19 +29,13 @@ You can run the <live-example></live-example> in Plunker and download the code f
You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with You can build forms by writing templates in the Angular [template syntax](guide/template-syntax) with
the form-specific directives and techniques described in this page. the form-specific directives and techniques described in this page.
<div class="l-sub-section"> <div class="l-sub-section">
You can also use a reactive (or model-driven) approach to build forms.
However, this page focuses on template-driven forms.
You can also use a reactive (or model-driven) approach to build forms.
However, this page focuses on template-driven forms.
</div> </div>
You can build almost any form with an Angular template&mdash;login forms, contact forms, and pretty much any business form. You can build almost any form with an Angular template&mdash;login forms, contact forms, and pretty much any business form.
You can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, You 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.
@ -51,13 +45,10 @@ otherwise wrestle with yourself.
You'll learn to build a template-driven form that looks like this: You'll learn to build a template-driven form that looks like this:
<figure> <figure>
<img src="generated/images/guide/forms/hero-form-1.png" alt="Clean Form"> <img src="generated/images/guide/forms/hero-form-1.png" alt="Clean Form">
</figure> </figure>
The *Hero Employment Agency* uses this form to maintain personal information about heroes. The *Hero Employment Agency* uses this form to maintain personal information about heroes.
Every hero needs a job. It's the company mission to match the right hero with the right crisis. Every hero needs a job. It's the company mission to match the right hero with the right crisis.
@ -65,27 +56,18 @@ Two of the three fields on this form are required. Required fields have a green
If you delete the hero name, the form displays a validation error in an attention-grabbing style: If you delete the hero name, the form displays a validation error in an attention-grabbing style:
<figure> <figure>
<img src="generated/images/guide/forms/hero-form-2.png" alt="Invalid, Name Required"> <img src="generated/images/guide/forms/hero-form-2.png" alt="Invalid, Name Required">
</figure> </figure>
Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red. Note that the *Submit* button is disabled, and the "required" bar to the left of the input control changes from green to red.
<div class="l-sub-section"> <div class="l-sub-section">
You can customize the colors and location of the "required" bar with standard CSS.
You can customize the colors and location of the "required" bar with standard CSS.
</div> </div>
You'll build this form in small steps: You'll build this form in small steps:
1. Create the `Hero` model class. 1. Create the `Hero` model class.
@ -98,11 +80,15 @@ You'll build this form in small steps:
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.
## Setup ## Setup
Follow the [setup](guide/setup) instructions for creating a new project Create a new project named <code>angular-forms</code>:
named angular-forms.
<code-example language="sh" class="code-shell">
ng new angular-forms
</code-example>
## Create the Hero model class ## Create the Hero model class
@ -113,15 +99,20 @@ A model can be as simple as a "property bag" that holds facts about a thing of a
That describes well the `Hero` class with its three required fields (`id`, `name`, `power`) That describes well the `Hero` class with its three required fields (`id`, `name`, `power`)
and one optional field (`alterEgo`). and one optional field (`alterEgo`).
In the `app` directory, create the following file with the given content: Using the Angular CLI, generate a new class named `Hero`:
<code-example language="sh" class="code-shell">
ng generate class Hero
</code-example>
With this content:
<code-example path="forms/src/app/hero.ts" title="src/app/hero.ts"> <code-example path="forms/src/app/hero.ts" title="src/app/hero.ts">
</code-example> </code-example>
It's an anemic model with few requirements and no behavior. Perfect for the demo. It's an anemic model with few requirements and no behavior. Perfect for the demo.
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
@ -131,28 +122,29 @@ The `alterEgo` is optional, so the constructor lets you omit it; note the questi
You can create a new hero like this: You can create a new hero like this:
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" region="SkyDog">
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (SkyDog)" region="SkyDog">
</code-example> </code-example>
## 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.
Begin with the class because it states, in brief, what the hero editor can do. Begin with the class because it states, in brief, what the hero editor can do.
Create the following file with the given content: Using the Angular CLI, generate a new component named `HeroForm`:
<code-example language="sh" class="code-shell">
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (v1)" region="v1"> ng generate component HeroForm
</code-example> </code-example>
With this content:
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (v1)" region="v1">
</code-example>
Theres nothing special about this component, nothing form-specific, Theres nothing special about this component, nothing form-specific,
nothing to distinguish it from any component you've written before. nothing to distinguish it from any component you've written before.
@ -173,21 +165,6 @@ parent component. This is not a concern now and these future changes won't affec
* You added a `diagnostic` property to return a JSON representation of the model. * You added a `diagnostic` property to return a JSON representation of the model.
It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later. It'll help you see what you're doing during development; you've left yourself a cleanup note to discard it later.
### Why the separate template file?
Why don't you write the template inline in the component file as you often do elsewhere?
There is no "right" answer for all occasions. Inline templates are useful when they are short.
Most form templates aren't short. TypeScript and JavaScript files generally aren't the best place to
write (or read) large stretches of HTML, and few editors help with files that have a mix of HTML and code.
Form templates tend to be large, even when displaying a small number of fields,
so it's usually best to put the HTML template in a separate file.
You'll write that template file in a moment. First,
revise the `app.module.ts` and `app.component.ts` to make use of the new `HeroFormComponent`.
## Revise *app.module.ts* ## Revise *app.module.ts*
`app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application `app.module.ts` defines the application's root module. In it you identify the external modules you'll use in the application
@ -196,89 +173,57 @@ and declare the components that belong to this module, such as the `HeroFormComp
Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of Because template-driven forms are in their own module, you need to add the `FormsModule` to the array of
`imports` for the application module before you can use forms. `imports` for the application module before you can use forms.
Replace the contents of the "QuickStart" version with the following: Update it with the following:
<code-example path="forms/src/app/app.module.ts" title="src/app/app.module.ts"> <code-example path="forms/src/app/app.module.ts" title="src/app/app.module.ts">
</code-example> </code-example>
<div class="l-sub-section"> <div class="l-sub-section">
There are two changes:
1. You import `FormsModule`.
There are three changes: 1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
access to all of the template-driven forms features, including `ngModel`.
1. You import `FormsModule` and the new `HeroFormComponent`.
1. You add the `FormsModule` to the list of `imports` defined in the `@NgModule` decorator. This gives the application
access to all of the template-driven forms features, including `ngModel`.
1. You add the `HeroFormComponent` to the list of `declarations` defined in the `@NgModule` decorator. This makes
the `HeroFormComponent` component visible throughout this module.
</div> </div>
<div class="alert is-important"> <div class="alert is-important">
If a component, directive, or pipe belongs to a module in the `imports` array, _don't_ re-declare it in the `declarations` array.
If you wrote it and it should belong to this module, _do_ declare it in the `declarations` array.
If a component, directive, or pipe belongs to a module in the `imports` array, _don't_ re-declare it in the `declarations` array.
If you wrote it and it should belong to this module, _do_ declare it in the `declarations` array.
</div> </div>
## Revise *app.component.html*
## Revise *app.component.ts*
`AppComponent` is the application's root component. It will host the new `HeroFormComponent`. `AppComponent` is the application's root component. It will host the new `HeroFormComponent`.
Replace the contents of the "QuickStart" version with the following: Replace the contents of its template with the following:
<code-example path="forms/src/app/app.component.html" title="src/app/app.component.html">
<code-example path="forms/src/app/app.component.ts" title="src/app/app.component.ts">
</code-example> </code-example>
<div class="l-sub-section"> <div class="l-sub-section">
There are only two changes.
The `template` is simply the new element tag identified by the component's `selector` property.
There are only two changes. This displays the hero form when the application component is loaded.
The `template` is simply the new element tag identified by the component's `selector` property. Don't forget to remove the `name` field from the class body as well.
This displays the hero form when the application component is loaded.
You've also dropped the `name` field from the class body.
</div> </div>
## Create an initial HTML form template ## Create an initial HTML form template
Create the template file with the following contents: Update the template file with the following contents:
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="start" title="src/app/hero-form/hero-form.component.html">
<code-example path="forms/src/app/hero-form.component.html" region="start" title="src/app/hero-form.component.html">
</code-example> </code-example>
The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and The language is simply HTML5. You're presenting two of the `Hero` fields, `name` and `alterEgo`, and
opening them up for user input in input boxes. opening them up for user input in input boxes.
@ -289,52 +234,34 @@ You added a *Submit* button at the bottom with some classes on it for styling.
*You're not using Angular yet*. There are no bindings or extra directives, just layout. *You're not using Angular yet*. There are no bindings or extra directives, just layout.
<div class="l-sub-section"> <div class="l-sub-section">
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
In template driven forms, if you've imported `FormsModule`, you don't have to do anything
to the `<form>` tag in order to make use of `FormsModule`. Continue on to see how this works.
</div> </div>
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/). These classes are purely cosmetic. come from [Twitter Bootstrap](http://getbootstrap.com/css/). These classes are purely cosmetic.
Bootstrap gives the form a little style. Bootstrap gives the form a little style.
<div class="callout is-important"> <div class="callout is-important">
<header>
Angular forms don't require a style library
</header>
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
<header> the styles of any external library. Angular apps can use any CSS library or none at all.
Angular forms don't require a style library
</header>
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.
</div> </div>
To add the stylesheet, open `styles.css` and add the following import line at the top:
<code-example path="forms/src/styles.1.css" linenums="false" title="src/styles.css">
To add the stylesheet, open `index.html` and add the following link to the `<head>`:
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (bootstrap)" region="bootstrap">
</code-example> </code-example>
## Add powers with _*ngFor_ ## Add powers with _*ngFor_
The hero must choose one superpower from a fixed list of agency-approved powers. The hero must choose one superpower from a fixed list of agency-approved powers.
@ -346,13 +273,10 @@ a technique seen previously in the [Displaying Data](guide/displaying-data) page
Add the following HTML *immediately below* the *Alter Ego* group: Add the following HTML *immediately below* the *Alter Ego* group:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (powers)" region="powers">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (powers)" region="powers">
</code-example> </code-example>
This code repeats the `<option>` 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;
you display its name using the interpolation syntax. you display its name using the interpolation syntax.
@ -363,13 +287,11 @@ you display its name using the interpolation syntax.
Running the app right now would be disappointing. Running the app right now would be disappointing.
<figure> <figure>
<img src="generated/images/guide/forms/hero-form-3.png" alt="Early form with no binding"> <img src="generated/images/guide/forms/hero-form-3.png" alt="Early form with no binding">
</figure> </figure>
You don't see hero data because you're not binding to the `Hero` yet. You don't see hero data because you're not binding to the `Hero` yet.
You know how to do that from earlier pages. You know how to do that from earlier pages.
[Displaying Data](guide/displaying-data) teaches property binding. [Displaying Data](guide/displaying-data) teaches property binding.
@ -384,113 +306,83 @@ makes binding the form to the model easy.
Find the `<input>` tag for *Name* and update it like this: Find the `<input>` tag for *Name* and update it like this:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-1">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-1">
</code-example> </code-example>
<div class="l-sub-section"> <div class="l-sub-section">
You added a diagnostic interpolation after the input tag
so you can see what you're doing.
You added a diagnostic interpolation after the input tag You left yourself a note to throw it away when you're done.
so you can see what you're doing.
You left yourself a note to throw it away when you're done.
</div> </div>
Focus on the binding syntax: `[(ngModel)]="..."`. Focus on the binding syntax: `[(ngModel)]="..."`.
You need one more addition to display the data. Declare You need one more addition to display the data. Declare
a template variable for the form. Update the `<form>` tag with a template variable for the form. Update the `<form>` tag with
`#heroForm="ngForm"` as follows: `#heroForm="ngForm"` as follows:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="template-variable">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="template-variable">
</code-example> </code-example>
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.
<div class="l-sub-section"> <div class="l-sub-section">
{@a ngForm} {@a ngForm}
### The _NgForm_ directive ### The _NgForm_ directive
What `NgForm` directive? What `NgForm` directive?
You didn't add an [NgForm](api/forms/NgForm) directive. You didn't add an [NgForm](api/forms/NgForm) directive.
Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag. Angular did. Angular automatically creates and attaches an `NgForm` directive to the `<form>` tag.
The `NgForm` directive supplements the `form` element with additional features.
It holds the controls you 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.
The `NgForm` directive supplements the `form` element with additional features.
It holds the controls you 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.
</div> </div>
If you ran the app now and started typing in the *Name* input box, If you ran the app now and started typing in the *Name* input box,
adding and deleting characters, you'd see them appear and disappear adding and deleting characters, you'd see them appear and disappear
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> <figure>
<img src="generated/images/guide/forms/ng-model-in-action.png" alt="ngModel in action"> <img src="generated/images/guide/forms/ng-model-in-action.png" alt="ngModel in action">
</figure> </figure>
The diagnostic is evidence that values really are flowing from the input box to the model and The diagnostic is evidence that values really are flowing from the input box to the model and
back again. back again.
<div class="l-sub-section"> <div class="l-sub-section">
That's *two-way data binding*.
For more information, see
That's *two-way data binding*. [Two-way binding with NgModel](guide/template-syntax#ngModel) on the
For more information, see the [Template Syntax](guide/template-syntax) page.
[Two-way binding with NgModel](guide/template-syntax#ngModel) on the
the [Template Syntax](guide/template-syntax) page.
</div> </div>
Notice that you also added a `name` attribute to the `<input>` tag and set it to "name", Notice that you also added a `name` attribute to the `<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.
<div class="l-sub-section"> <div class="l-sub-section">
Internally, Angular creates `FormControl` instances and
registers them with an `NgForm` directive that Angular attached to the `<form>` tag.
Internally, Angular creates `FormControl` instances and Each `FormControl` is registered under the name you assigned to the `name` attribute.
registers them with an `NgForm` directive that Angular attached to the `<form>` tag. Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
Each `FormControl` is registered under the name you assigned to the `name` attribute.
Read more in the previous section, [The NgForm directive](guide/forms#ngForm).
</div> </div>
Add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*. Add similar `[(ngModel)]` bindings and `name` attributes to *Alter Ego* and *Hero Power*.
You'll ditch the input box binding message You'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.
@ -498,42 +390,29 @@ Then you can confirm that two-way data binding works *for the entire hero model*
After revision, the core of the form should look like this: After revision, the core of the form should look like this:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModel-2">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModel-2">
</code-example> </code-example>
<div class="l-sub-section"> <div class="l-sub-section">
* 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 an `id` property that is used by the `label` element's `for` attribute * Each input element has a `name` property that is required by Angular forms to register the control with the form.
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.
</div> </div>
If you run the app now and change every hero model property, the form might display like this: If you run the app now and change every hero model property, the form might display like this:
<figure> <figure>
<img src="generated/images/guide/forms/ng-model-in-action-2.png" alt="ngModel in action"> <img src="generated/images/guide/forms/ng-model-in-action-2.png" alt="ngModel in action">
</figure> </figure>
The diagnostic near the top of the form The diagnostic near the top of the form
confirms that all of your changes are reflected in the model. confirms that all of your 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.
## Track control state and validity with _ngModel_ ## Track control state and validity with _ngModel_
Using `ngModel` in a form gives you more than just two-way data binding. It also tells Using `ngModel` in a form gives you more than just two-way data binding. It also tells
@ -542,7 +421,6 @@ you if the user touched the control, if the value changed, or if the value becam
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.
You can leverage those class names to change the appearance of the control. You can leverage those class names to change the appearance of the control.
<table> <table>
<tr> <tr>
@ -611,18 +489,13 @@ You can leverage those class names to change the appearance of the control.
</table> </table>
Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy` Temporarily add a [template reference variable](guide/template-syntax#ref-vars) named `spy`
to the _Name_ `<input>` tag and use it to display the input's CSS classes. to the _Name_ `<input>` tag and use it to display the input's CSS classes.
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="ngModelName-2">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="ngModelName-2">
</code-example> </code-example>
Now run the app and look at the _Name_ input box. Now run the app and look at the _Name_ input box.
Follow these steps *precisely*: Follow these steps *precisely*:
@ -633,61 +506,44 @@ Follow these steps *precisely*:
The actions and effects are as follows: The actions and effects are as follows:
<figure> <figure>
<img src="generated/images/guide/forms/control-state-transitions-anim.gif" alt="Control State Transition"> <img src="generated/images/guide/forms/control-state-transitions-anim.gif" alt="Control State Transition">
</figure> </figure>
You should see the following transitions and class names: You should see the following transitions and class names:
<figure> <figure>
<img src="generated/images/guide/forms/ng-control-class-changes.png" alt="Control state transitions"> <img src="generated/images/guide/forms/ng-control-class-changes.png" alt="Control state transitions">
</figure> </figure>
The `ng-valid`/`ng-invalid` pair is the most interesting, because you want to send a The `ng-valid`/`ng-invalid` pair is the most interesting, because you want to send a
strong visual signal when the values are invalid. You also want to mark required fields. strong visual signal when the values are invalid. You also want to mark required fields.
To create such visual feedback, add definitions for the `ng-*` CSS classes. To create such visual feedback, add definitions for the `ng-*` CSS classes.
*Delete* the `#spy` template reference variable and the `TODO` as they have served their purpose. *Delete* the `#spy` template reference variable and the `TODO` as they have served their purpose.
## Add custom CSS for visual feedback ## Add custom CSS for visual feedback
You can mark required fields and invalid data at the same time with a colored bar You 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> <figure>
<img src="generated/images/guide/forms/validity-required-indicator.png" alt="Invalid Form"> <img src="generated/images/guide/forms/validity-required-indicator.png" alt="Invalid Form">
</figure> </figure>
You achieve this effect by adding these class definitions to a new `forms.css` file You achieve this effect by adding these class definitions to a new `forms.css` file
that you add to the project as a sibling to `index.html`: that you add to the project as a sibling to `index.html`:
<code-example path="forms/src/assets/forms.css" title="src/assets/forms.css"> <code-example path="forms/src/assets/forms.css" title="src/assets/forms.css">
</code-example> </code-example>
Update the `<head>` of `index.html` to include this style sheet: Update the `<head>` of `index.html` to include this style sheet:
<code-example path="forms/src/index.html" linenums="false" title="src/index.html (styles)" region="styles"> <code-example path="forms/src/index.html" linenums="false" title="src/index.html (styles)" region="styles">
</code-example> </code-example>
## Show and hide validation error messages ## Show and hide validation error messages
You can improve the form. The _Name_ input box is required and clearing it turns the bar red. You can improve the form. The _Name_ input box is required and clearing it turns the bar red.
@ -696,13 +552,10 @@ Leverage the control's state to reveal a helpful message.
When the user deletes the name, the form should look like this: When the user deletes the name, the form should look like this:
<figure> <figure>
<img src="generated/images/guide/forms/name-required-error.png" alt="Name required"> <img src="generated/images/guide/forms/name-required-error.png" alt="Name required">
</figure> </figure>
To achieve this effect, extend the `<input>` tag with the following: To achieve this effect, extend the `<input>` tag with the following:
* A [template reference variable](guide/template-syntax#ref-vars). * A [template reference variable](guide/template-syntax#ref-vars).
@ -710,41 +563,29 @@ To achieve this effect, extend the `<input>` tag with the following:
Here's an example of an error message added to the _name_ input box: Here's an example of an error message added to the _name_ input box:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="name-with-error-msg">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="name-with-error-msg">
</code-example> </code-example>
You need a template reference variable to access the input box's Angular control from within the template. You need a template reference variable to access the input box's Angular control from within the template.
Here you created a variable called `name` and gave it the value "ngModel". Here you created a variable called `name` and gave it the value "ngModel".
<div class="l-sub-section"> <div class="l-sub-section">
Why "ngModel"?
A directive's [exportAs](api/core/Directive) property
Why "ngModel"? tells Angular how to link the reference variable to the directive.
A directive's [exportAs](api/core/Directive) property You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
tells Angular how to link the reference variable to the directive.
You set `name` to `ngModel` because the `ngModel` directive's `exportAs` property happens to be "ngModel".
</div> </div>
You control visibility of the name error message by binding properties of the `name` You control visibility of the name error message by binding properties of the `name`
control to the message `<div>` element's `hidden` property. control to the message `<div>` element's `hidden` property.
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (hidden-error-msg)" region="hidden-error-msg">
</code-example> </code-example>
In this example, you hide the message when the control is valid or pristine; In this example, you 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.
@ -767,19 +608,14 @@ power to valid values.
Now you'll add a new hero in this form. Now you'll add a new hero in this form.
Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method. Place a *New Hero* button at the bottom of the form and bind its click event to a `newHero` component method.
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-no-reset" title="src/app/hero-form/hero-form.component.html (New Hero button)">
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-no-reset" title="src/app/hero-form.component.html (New Hero button)">
</code-example> </code-example>
<code-example path="forms/src/app/hero-form/hero-form.component.ts" region="new-hero" title="src/app/hero-form/hero-form.component.ts (New Hero method)" linenums="false">
<code-example path="forms/src/app/hero-form.component.ts" region="new-hero" title="src/app/hero-form.component.ts (New Hero method)" linenums="false">
</code-example> </code-example>
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.
That's understandable as these are required fields. That's understandable as these are required fields.
@ -797,17 +633,12 @@ Replacing the hero object *did not restore the pristine state* of the form contr
You have to clear all of the flags imperatively, which you can do You have to clear all of the flags imperatively, which you 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.
<code-example path="forms/src/app/hero-form/hero-form.component.html" region="new-hero-button-form-reset" title="src/app/hero-form/hero-form.component.html (Reset the form)">
<code-example path="forms/src/app/hero-form.component.html" region="new-hero-button-form-reset" title="src/app/hero-form.component.html (Reset the form)">
</code-example> </code-example>
Now clicking "New Hero" resets both the form and its control flags. Now clicking "New Hero" resets both the form and its control flags.
## 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.
@ -819,13 +650,10 @@ A "form submit" is useless at the moment.
To make it useful, bind the form's `ngSubmit` event property To make it useful, bind the form's `ngSubmit` event property
to the hero form component's `onSubmit()` method: to the hero form component's `onSubmit()` method:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (ngSubmit)" region="ngSubmit">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (ngSubmit)" region="ngSubmit">
</code-example> </code-example>
You'd already defined a template reference variable, You'd already defined a template reference variable,
`#heroForm`, and initialized it with the value "ngForm". `#heroForm`, and initialized it with the value "ngForm".
Now, use that variable to access the form with the Submit button. Now, use that variable to access the form with the Submit button.
@ -835,13 +663,10 @@ You'll bind the form's overall validity via
the `heroForm` variable to the button's `disabled` property the `heroForm` variable to the button's `disabled` property
using an event binding. Here's the code: using an event binding. Here's the code:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (submit-button)" region="submit-button">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (submit-button)" region="submit-button">
</code-example> </code-example>
If you run the application now, you find that the button is enabled&mdash;although If you run the application now, you find that the button is enabled&mdash;although
it doesn't do anything useful yet. it doesn't do anything useful yet.
@ -857,65 +682,48 @@ For you, it was as simple as this:
1. Define a template reference variable on the (enhanced) form element. 1. Define a template reference variable on the (enhanced) form element.
2. Refer to that variable in a button many lines away. 2. Refer to that variable in a button many lines away.
## 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.
<div class="l-sub-section"> <div class="l-sub-section">
An unsurprising observation for a demo. To be honest,
jazzing it up won't teach you anything new about forms.
An unsurprising observation for a demo. To be honest, But this is an opportunity to exercise some of your newly won
jazzing it up won't teach you anything new about forms. binding skills.
But this is an opportunity to exercise some of your newly won If you aren't interested, skip to this page's conclusion.
binding skills.
If you aren't interested, skip to this page's conclusion.
</div> </div>
For a more strikingly visual effect, For a more strikingly visual effect,
hide the data entry area and display something else. hide the data entry area and display something else.
Wrap the form in a `<div>` and bind Wrap the form in a `<div>` and bind
its `hidden` property to the `HeroFormComponent.submitted` property. its `hidden` property to the `HeroFormComponent.submitted` property.
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="edit-div">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="edit-div">
</code-example> </code-example>
The main form is visible from the start because the The main form is visible from the start because the
`submitted` property is false until you submit the form, `submitted` property is false until you submit the form,
as this fragment from the `HeroFormComponent` shows: as this fragment from the `HeroFormComponent` shows:
<code-example path="forms/src/app/hero-form/hero-form.component.ts" linenums="false" title="src/app/hero-form/hero-form.component.ts (submitted)" region="submitted">
<code-example path="forms/src/app/hero-form.component.ts" linenums="false" title="src/app/hero-form.component.ts (submitted)" region="submitted">
</code-example> </code-example>
When you click the *Submit* button, the `submitted` flag becomes true and the form disappears When you click the *Submit* button, the `submitted` flag becomes true and the form disappears
as planned. as planned.
Now the app needs 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 HTML below the `<div>` wrapper you just wrote: Add the following HTML below the `<div>` wrapper you just wrote:
<code-example path="forms/src/app/hero-form/hero-form.component.html" linenums="false" title="src/app/hero-form/hero-form.component.html (excerpt)" region="submitted">
<code-example path="forms/src/app/hero-form.component.html" linenums="false" title="src/app/hero-form.component.html (excerpt)" region="submitted">
</code-example> </code-example>
There's the hero again, displayed read-only with interpolation bindings. There's the hero again, displayed read-only with interpolation bindings.
This `<div>` appears only while the component is in the submitted state. This `<div>` appears only while the component is in the submitted state.
@ -924,9 +732,7 @@ that clears the `submitted` flag.
When you click the *Edit* button, this block disappears and the editable form reappears. When you click the *Edit* button, this block disappears and the editable form reappears.
## Summary
## Conclusion
The Angular form discussed in this page takes advantage of the following The Angular form discussed in this page takes 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:
@ -941,89 +747,15 @@ framework features to provide support for data modification, validation, and mor
* 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.
The final project folder structure should look like this:
<div class='filetree'>
<div class='file'>
angular-forms
</div>
<div class='children'>
<div class='file'>
src
</div>
<div class='children'>
<div class='file'>
app
</div>
<div class='children'>
<div class='file'>
app.component.ts
</div>
<div class='file'>
app.module.ts
</div>
<div class='file'>
hero.ts
</div>
<div class='file'>
hero-form.component.html
</div>
<div class='file'>
hero-form.component.ts
</div>
</div>
<div class='file'>
main.ts
</div>
<div class='file'>
tsconfig.json
</div>
<div class='file'>
index.html
</div>
</div>
<div class='file'>
node_modules ...
</div>
<div class='file'>
package.json
</div>
</div>
</div>
Heres the code for the final version of the application: Heres the code for the final version of the application:
<code-tabs> <code-tabs>
<code-pane title="hero-form.component.ts" path="forms/src/app/hero-form.component.ts" region="final"> <code-pane title="hero-form/hero-form.component.ts" path="forms/src/app/hero-form/hero-form.component.ts" region="final">
</code-pane> </code-pane>
<code-pane title="hero-form.component.html" path="forms/src/app/hero-form.component.html" region="final"> <code-pane title="hero-form/hero-form.component.html" path="forms/src/app/hero-form/hero-form.component.html" region="final">
</code-pane> </code-pane>
@ -1035,6 +767,10 @@ Heres the code for the final version of the application:
</code-pane> </code-pane>
<code-pane title="app.component.html" path="forms/src/app/app.component.html">
</code-pane>
<code-pane title="app.component.ts" path="forms/src/app/app.component.ts"> <code-pane title="app.component.ts" path="forms/src/app/app.component.ts">
</code-pane> </code-pane>
@ -1043,10 +779,6 @@ Heres the code for the final version of the application:
</code-pane> </code-pane>
<code-pane title="index.html" path="forms/src/index.html">
</code-pane>
<code-pane title="forms.css" path="forms/src/assets/forms.css"> <code-pane title="forms.css" path="forms/src/assets/forms.css">
</code-pane> </code-pane>