(docs) forms - code maintained by _examples

Includes template-syntax.jade clarification on attributes v properties
This commit is contained in:
Ward Bell 2015-10-20 16:10:44 -07:00 committed by Naomi Black
parent f10a2e906a
commit 963f67ea07
22 changed files with 946 additions and 466 deletions

View File

@ -0,0 +1 @@
src/**/*.js

View File

@ -0,0 +1,22 @@
{
"name": "angular2-getting-started",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc -p src -w",
"start": "live-server --open=src"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"angular2": "2.0.0-alpha.44",
"bootstrap": "^3.3.5",
"systemjs": "0.19.2"
},
"devDependencies": {
"live-server": "^0.8.1",
"typescript": "^1.6.2"
}
}

View File

@ -0,0 +1,12 @@
// #docregion
import {bootstrap, Component} from 'angular2/angular2'
import {HeroFormComponent} from './hero-form.component'
@Component({
selector: 'my-app',
template: '<hero-form></hero-form>',
directives: [HeroFormComponent]
})
class AppComponent { }
bootstrap(AppComponent);

View File

@ -0,0 +1,196 @@
<!--#docplaster
-->
<!-- #docregion final -->
<div class="container">
<!-- #docregion edit-div -->
<div [hidden]="submitted">
<h1>Hero Form</h1>
<!-- #docregion ng-submit -->
<form (ng-submit)="onSubmit()" #hf="form">
<!-- #enddocregion edit-div -->
<!-- #enddocregion ng-submit -->
<div class="form-group">
<label for="name">Name</label>
<!-- #docregion name-with-error-msg -->
<input type="text" class="form-control" required
[(ng-model)]="model.name"
ng-control="name" #name="form" >
<div [hidden]="name.valid" class="alert alert-danger">
Name is required
</div>
<!-- #enddocregion name-with-error-msg -->
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ng-model)]="model.alterEgo"
ng-control="alterEgo" >
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ng-model)]="model.power"
ng-control="power" #power="form" >
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
</select>
<div [hidden]="power.valid" class="alert alert-danger">
Power is required
</div>
</div>
<!-- #docregion submit-button -->
<button type="submit" class="btn btn-default"
[disabled]="!hf.form.valid">Submit</button>
<!-- #enddocregion submit-button -->
</form>
</div>
<!-- #docregion submitted -->
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">Name</div>
<div class="col-xs-9 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">Alter Ego</div>
<div class="col-xs-9 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-xs-3">Power</div>
<div class="col-xs-9 pull-left">{{ model.power }}</div>
</div>
<br>
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
<!-- #enddocregion submitted -->
</div>
<!-- #enddocregion final -->
<!-- ==================================================== -->
<div>
<form>
<!-- #docregion edit-div -->
<!-- ... all of the form ... -->
</form>
</div>
<!-- #enddocregion edit-div -->
<!-- ==================================================== -->
<hr>
<style>
.no-style .ng-valid {
border-left: 1px solid #CCC
}
.no-style .ng-invalid {
border-left: 1px solid #CCC
}
</style>
<div class="no-style" style="margin-left: 4px">
<!-- #docregion start -->
<div class="container">
<h1>Hero Form</h1>
<form>
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required>
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control">
</div>
<!-- #enddocregion start -->
<!-- #docregion powers -->
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required>
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion powers -->
<!-- #docregion start -->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion start -->
<!-- #enddocregion phase1-->
<!-- ==================================================== -->
<hr>
<!-- #docregion phase2-->
<div class="container">
<h1>Hero Form</h1>
<form>
<!-- #docregion ng-model-2-->
{{diagnostic}}
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" required
[(ng-model)]="model.name" >
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control"
[(ng-model)]="model.alterEgo">
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" required
[(ng-model)]="model.power" >
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
</select>
</div>
<!-- #enddocregion ng-model-2-->
<button type="submit" class="btn btn-default">Submit</button>
</form>
</div>
<!-- #enddocregion phase2-->
<!-- EXTRA MATERIAL FOR DOCUMENTATION -->
<hr>
<!-- #docregion ng-model-1-->
<input type="text" class="form-control" required
[(ng-model)]="model.name" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ng-model-1-->
<hr>
<!-- #docregion ng-model-3-->
<input type="text" class="form-control" required
[ng-model]="model.name"
(ng-model-change)="model.name = $event" >
TODO: remove this: {{model.name}}
<!-- #enddocregion ng-model-3-->
<hr>
<form>
<!-- #docregion ng-control-1 -->
<input type="text" class="form-control" required
[(ng-model)]="model.name"
ng-control="name" >
<!-- #enddocregion ng-control-1 -->
<hr>
<!-- #docregion ng-control-2 -->
<input type="text" class="form-control" required
[(ng-model)]="model.name"
ng-control="name" #spy >
TODO: remove this: {{spy.className}}
<!-- #enddocregion ng-control-2 -->
</form>
<div>
<hr>
Name via form.controls = {{showFormControls(hf)}}
</div>
</div>

View File

@ -0,0 +1,48 @@
// #docplaster
// #docregion
// #docregion first, final
import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2';
import { Hero } from './hero';
@Component({
selector: 'hero-form',
templateUrl: 'app/hero-form.component.html',
// #docregion directives
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
// #enddocregion
})
export class HeroFormComponent {
powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
model = new Hero(18, 'Dr IQ', this.powers[0], 'Chuck Overstreet');
// #docregion submitted
submitted = false;
onSubmit() { this.submitted = true; }
// #enddocregion submitted
// #enddocregion final
// TODO: Remove this when we're done
get diagnostic() { return JSON.stringify(this.model); }
// #enddocregion first
//////// DO NOT SHOW IN DOCS ////////
// Reveal in html:
// AlterEgo via form.controls = {{showFormControls(hf)}}
showFormControls(form){
return form.controls.alterEgo &&
// #docregion form-controls
form.controls.name.value; // Dr. IQ
// #enddocregion form-controls
}
/////////////////////////////
// #docregion first, final
}
// #enddocregion first, final

View File

@ -0,0 +1,11 @@
// #docregion
export class Hero {
constructor(
public id: number,
public name: string,
public power: string,
public alterEgo?: string
) { }
}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<!-- #docregion -->
<html>
<head>
<title>Hero Form</title>
<!-- #docregion bootstrap -->
<link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap.min.css">
<!-- #enddocregion bootstrap -->
<link rel="stylesheet" href="styles.css">
<!-- #docregion libraries -->
<script src="../node_modules/systemjs/dist/system.src.js"></script>
<script src="../node_modules/angular2/bundles/angular2.dev.js"></script>
<!-- #enddocregion libraries -->
<!-- #docregion systemjs -->
<script>
System.config({
packages: {'app': {defaultExtension: 'js'}}
});
System.import('app/app');
</script>
<!-- #enddocregion systemjs -->
</head>
<body>
<my-app>Loading...</my-app>
</body>
</html>

View File

@ -0,0 +1,9 @@
/* #docregion */
.ng-valid[required] {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid {
border-left: 5px solid #a94442; /* red */
}
/* #enddocregion */

View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"target": "ES5",
"module": "commonjs",
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"noImplicitAny": false
}
}

View File

@ -19,6 +19,11 @@
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
},
"forms": {
"title": "Forms",
"intro": "A form creates a cohesive, effective, and compelling data entry experience. An Angular form coordinates a set of data-bound user controls, tracks changes, validates input, and presents errors."
},
"pipes": {
"title": "Pipes",
"intro": "Pipes transform displayed values within a template"
@ -29,11 +34,6 @@
"intro": "How to write templates that display data and consume user events with the help of data binding."
},
"forms": {
"title": "Angular 2 Forms",
"intro": "Learn about the different approaches we can take when building forms and see examples of them in action."
},
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Angular's dependency injection system creates and delivers dependent services \"just-in-time\"."

View File

@ -1,464 +1,579 @@
include ../../../../_includes/_util-fns
<!-- http://plnkr.co/edit/wg154K -->
:markdown
Weve all used a form to login, submit a help request, place an order, book a flight,
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.
It's more challenging to create a cohesive data entry experience that guides the
user efficiently and effectively through the workflow behind the form.
*That* takes design skills that are, to be frank, well out of scope for this chapter.
It also takes framework support for
**two-way data binding, change tracking, validation, and error handling**
... which we shall cover in this chapter on Angular forms.
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
- the `ng-model` two-way data binding syntax for reading and writing values to input controls
- the `ng-control` directive to track the change state and validity of form controls
- the special CSS classes that `ng-control` adds to form controls and how we can use them to provide strong visual feedback
- how to display validation errors to users and enable/disable form controls
- how to share information across controls with template local variables
.l-main-section
:markdown
Forms play an important role in Web applications whether its collecting data or allowing data to be modified or deleted. Weve all used a form to login to an application, submit a help request to a website, manipulate business data and more. While creating custom forms in HTML is a relatively straightforward process, the complexity increases when a form requires data binding, event handling, validation rules and error messages.
## Template-Driven Forms
Angular 2 provides several key features that can be used to build any type of form your application may need whether its a large enterprise line of business form or a small form used to log a user into an application. Throughout this chapter well learn about the different approaches we can take when building forms and see examples of them in action.
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 chapter.
.l-sub-section
:markdown
That's not the only way to create a form but it's the way we'll cover in this chapter.
:markdown
We can build almost any form we need with an Angular template ... login forms, contact forms ... pretty much any business forms.
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.
The overall learning goals of this chapter include:
It will be pretty easy because Angular handles many of the repeative, boiler plate tasks we'd
otherwise wrestle with ourselves.
- Learn how to build an Angular 2 form component and template.
- Learn data binding syntax that can be used to minimize the amount of code you write when building forms.
- Learn about key Angular 2 directives that can be used in forms.
- Learn how to display errors to users and enable/disable form controls.
- Learn what template local variables are and how they can be used.
- Learn about CSS classes that Angular 2 adds to form controls and understand how they can be used to provide visual feedback to users.
Lets get started by taking a look at the process we can follow to create template-driven forms in Angular 2.
## Introduction to Template-Driven Forms
We can use template-driven forms to create login forms, contact forms and custom line of business forms in our Angular 2 applications. We can create any type of form layout, bind data to form controls, display validation errors, disable specific form controls when a form is invalid, provide visual feedback to users, plus more. In scenarios where we need to capture data from end users or allow them to modify existing data, template-driven forms provide a robust option that can be created quickly and easily using Angular 2 features.
Heres an example of a simple template-driven form in action. Well discuss the form throughout this chapter and learn how to build it.
We'll discuss and learn to build the following template-driven form:
figure.image-display
img(src="/resources/images/devguide/forms/tdf-1.png" alt="Clean Form")
img(src="/resources/images/devguide/forms/hf-1.png" alt="Clean Form")
:markdown
Heres the same form displaying a validation error and providing additional visual feedback to the end user:
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!
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:
figure.image-display
img(src="/resources/images/devguide/forms/tdf-2.png" alt="Invalid, Name Required")
img(src="/resources/images/devguide/forms/hf-2.png" alt="Invalid, Name Required")
:markdown
If the user doesnt satisfy all of the validation requirements for the form, the submit button will automatically be disabled and the left portion of the input control that has an error will change from green to red. The colors used, location of the colors and more can be customized using standard CSS. Required controls have a red left-border to make them easy to visually identify for end users:
Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red.
figure.image-display
img(src="/resources/images/devguide/forms/tdf-3.png" alt="Invalid Form")
.l-sub-section
p We'll' customize the colors and location of the "required" bar with standard CSS.
:markdown
What features does Angular 2 provide that we can use to create this type of template-driven form? Well answer this question and others by walking through the following steps:
We will build this form in the following sequence of small steps
1. Create an HTML form template
1. Create a model class
1. Create a form component
1. Add the **ng-submit** directive to the form
1. Add the **ng-control** directive to each form input control
1. Create the `Hero` model class
1. Create the component that controls the form
1. Create a template with the initial form layout
1. Add the **ng-model** directive to each form input control
1. Disable the forms submit button until the form is valid
1. Add the **ng-control** directive 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 **ng-submit**
1. Disable the forms submit button until the form is valid
The following sections provide a more detailed look at these steps and the code that well need to write to make it all happen. Lets start by looking at the initial form template.
.l-main-section
:markdown
## Create the Hero Model Class
### Create an HTML Form Template
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.
We can use a standard HTML form with the Angular template-driven approach. An example of a form template named **template-form.component.html** is shown next. The HTML content includes the form element and associated inputs as well as a section after the form thats used to display data submitted by the end user.
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`).
code-example(format="linenums" language="html").
&lt;div class="container">
&lt;div [hidden]="submitted">
&lt;h1>Template Driven Form&lt;/h1>
&lt;form>
&lt;div class="form-group">
&lt;label for="name">Name&lt;/label>
&lt;input type="text" class="form-control" required>
&lt;/div>
&lt;div class="form-group">
&lt;label for="alterEgo">Alter Ego&lt;/label>
&lt;input type="text" class="form-control" required>
&lt;/div>
&lt;div class="form-group">
&lt;label for="power">Hero Power&lt;/label>
&lt;select class="form-control" required>
&lt;option *ng-for="#p of powers" [value]="p">{{p}}&lt;/option>
&lt;/select>
&lt;/div>
&lt;button type="submit" class="btn btn-default">Submit&lt;/button>
&lt;/form>
&lt;/div>
&lt;div [hidden]="!submitted">
&lt;h2>You submitted the following:&lt;/h2>
&lt;div class="row">
&lt;div class="col-md-2">Name&lt;/div>
&lt;div class="col-md-10 pull-left">{{ model.name }}&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col-md-2">Alter Ego&lt;/div>
&lt;div class="col-md-10 pull-left">
{{ model.alterEgo }}
&lt;/div>
&lt;/div>
&lt;div class="row">
&lt;div class="col-md-2">Power&lt;/div>
&lt;div class="col-md-10 pull-left">
{{ model.power }}
&lt;/div>
&lt;/div>
&lt;br />
&lt;button class="btn btn-default" (click)="submitted=false">Edit&lt;/button>
&lt;/div>
&lt;/div>
Create a new file called `hero.ts` and give it the following class definition:
+makeExample('forms/ts/src/app/hero.ts')
:markdown
The form captures Name, Alter Ego and Power values and makes all three of the inputs required by using the **required** attribute. The select control in the form iterates through an array of powers and displays them as select options using the Angular 2 **ng-for** directive.
The application switches between the form view and the data display view by toggling a **submitted** property that well discuss later in this chapter. The **submitted** property is bound to the **hidden** property of the div containers that wrap the form section and data display section.
Now that the template for the form is in place, we can move on to discussing the model object that is bound to the form.
## Create a Model Class
As users interacts with the form well capture the data they enter or modify in instances of a “model” class. A model class is simply a class that holds application data. In this example **Hero** is the model class and it has **id**, **name**, **power**, and **alterEgo** properties.
Heres the complete code for the Hero model (hero.ts):
It's an anemic model with few requirements and no behavior. Perfect for our demo.
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 like this:
```
export class Hero {
constructor(
public id?: number,
public name?: string,
public power?: string,
public alterEgo?: string
) { }
}
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"
```
The TypeScript compiler generates a public field for each **public** constructor parameter and assigns the parameters value to that field automatically. For example, TypeScript generates an **alterEgo** field to match the **public alterEgo** constructor parameter and sets the alterEgo field to the optional value of the fourth argument. This is a convenient way to initialize an object through the constructor with minimal code.
Lets create a form component that uses this **Hero** model class.
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
.l-main-section
:markdown
## Create a Form component
The HTML form template shown earlier is placed directly in an Angular component using the **View** annotations **template** property. Alternatively, we can create a separate file and link it to the component using the **View** annotations **templateUrl** property. Which approach is better? Theres no “right” answer since its very subjective, but in cases where a template contains a lot of HTML content, having a separate template file can be helpful and minimize the overall size of the component.
An Angular form has two parts: an HTML-based template and a code-based Component to handle data and user interactions.
In this example well link the template to a component named **TemplateFormComponent (template-form.component.ts)** using the **templateUrl** property of **View** annotation. The complete code for **TemplateFormComponent** is shown next:
```
import {Component, View, NgFor, FORM_DIRECTIVES} from 'angular2/angular2';
We begin with the Component because it states, in brief, what the Hero editor can do.
import { Hero } from './hero';
Create a new file called `hero-form.component.ts` and give it the following definition:
@Component({
selector: 'template-driven-form',
templateUrl: 'app/template-form.component.html',
directives: [FORM_DIRECTIVES, NgFor]
})
export class TemplateFormComponent {
+makeExample('forms/ts/src/app/hero-form.component.ts', 'first')
:markdown
Theres nothing special about this component, nothing to distinguish it from any component we've written before,
nothing form-specific about it ... except, perhaps, the tell-tale `FORM_DIRECTIVES` import.
model: Hero;
powers: string[];
submitted: boolean = false;
Understanding this component requires only the Angular 2 concepts weve learned in previous chapters
constructor() {
this.model = new Hero(18,'Dr IQ', 'Really Smart', 'Chuck Overstreet');
1. We import a standard set of symbols from the Angular library.
We don't have a template yet but we usually import `CORE_DIRECTIVES` and it doesn't surprise us to
import something called `FORM_DIRECTIVES`, given that we'll be writing a form
this.powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
}
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
onSubmit() {
this.submitted = true;
}
1. The `templateUrl` property points to a separate file for template HTML called `hero-form.component.html`.
}
```
We can see that theres nothing unique about this component even though were using it to create a form. Many of the Angular 2 concepts weve already learned apply. Here are few of the key features shown in the component:
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
parent component. None of this concerns us now and these future changes won't affect our form.
1. We import **NgFor, Component, View** and **FORM_DIRECTIVES** as well as the the **Hero** model class. Ng
1. We add the **Component** selector value of “template-driven-form”. We also define the form template **(template-form.component.html)** using the **templateUrl** property and all of the directives well use in the form template using the **directives** property.
1. The **TemplateFormComponent** class **model, powers** and **submitted** properties <span style="background-color:pink" >defined</span>.
1. The constructor initializes the model and powers properties with values.
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.
Weve now linked the form template to the form component and integrated the **Hero** model class into the component. The next step is to integrate Angular 2 directives into the form.
We may wonder why we aren't writing the template inline in the component file as we have often done
elsewhere in the Developer Guide.
## Add the ng-submit Directive to the Form
There is no “right” answer for all occasions. We kind of like inline templates when they are short.
Most form templates won't be short. TypeScript and JavaScript files generally aren't the best place to
write (or read) large stretches of HTML and few editors are much help with files that have a mix of HTML and code.
We also like short files with a clear and obvious purpose like this one.
Well now shift our attention back to the form template **(template-form.component.html)** created earlier. Although the form is linked to the component, it wont do much at this point aside from showing the different form controls. Angular 2 directives have to be added into the form so that we can bind the Hero model and perform other tasks such as validation.
We made a good choice to put the HTML template elsewhere. Let's write it.
To start, we can add the **ng-submit** event to the **form** element and hook it to the **onSubmit()** function shown earlier in the **TemplateFormComponent class**:
```
<form (ng-submit)="onSubmit()" #f="form">
```
its important to note that **form** is a directive in this case that exposes an **ng-submit** event. Were using the event binding syntax in this example to wire **ng-submit** to **onSubmit()**. The **#f** attribute (which is referred to as a “template local variable”) makes the form object available as a local variable named “f” within the template. Well be using the local variable later to handle disabling the submit button when the form isnt valid.
.l-main-section
:markdown
## Create an initial HTML Form Template
Now that weve added the ability to submit the form, lets discuss different form directives that can be added to the forms input controls.
Create a new template file called `hero-form.component.html` and give it the following definition:
+makeExample('forms/ts/src/app/hero-form.component.html', 'start')
## Add the ng-control Directive
:markdown
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
opening them up for user input in input boxes.
Angular 2 provides several key directives named **ng-model** and **ng-control** that can be used in forms to handle data binding and validation/change tracking tasks respectively. Both directives can be used together to simplify the overall process of creating custom forms and capturing and storing input from end users.
The "Name" `<input>` control has the HTML5 `required` attribute;
the "Alter Ego" `<input>` control does not because `alterEgo` is optional.
The **ng-control** directive provides support for tracking the state of controls (**dirty, errors, pristine, touched** and **untouched**) and determining if theyre valid or not.
We've got a "Submit" button at the bottom with some classes on it.
is modified by an end user, **ng-control** updates its state properties as the user enters data a control so that we can know when a control is dirty, pristine, touched or has errors.
**We are not using Angular yet**. There are no bindings. No extra directives. Just layout.
The **ng-control** directive also provides support for checking the validity of a form control through its **valid** property. For example, the **valid** property of a form input control that has with an HTML5 **required** attribute added to it will have its is false until the user doesnt supplies a value. This can be useful when checking the validity of an individual form control or the form as a whole.
The `container`,`form-group`, `form-control`, and `btn` classes are CSS Bootstrap. Purely cosmetic.
We're using Bootstrap to gussy up our form.
Hey, what's a form without a little style!
Heres an example of the **ng-control** directive applied to a textbox:
```
<input type="text"
ng-control="alterEgo"
required>
```
In this example were assigning the **ng-control** directive to a string value of “alterEgo”. By adding the directive to a form input control we automatically get access to the properties discussed earlier (dirty, pristine, etc.) and also get programmatic access to the form control which can be useful for custom validation scenarios. Later in this chapter well see how the value assigned to **ng-control** can be used to show users error messages when they dont satisfy the requirements of a given form control.
.l-sub-section
:markdown
Since we're using [CSS Boostrap](http://getbootstrap.com/css/).
now might be a good time to install it into our project.
We can do that with npm.
## Add the ng-model Directive
Open a terminal window and enter the command:
code-example(language="html" escape="html").
npm install bootstrap
:markdown
<br>Open the `index.html` and add the following line wherever we like to put our CSS
+makeExample('forms/ts/src/index.html', 'bootstrap')
The **ng-control** directive can be combined with another directive called **ng-model** on form controls. The **ng-model** directive can be used to bind data to form controls and also listen for changes to the data through an **ng-model** event. This is useful anytime we need to bind a model object property to textbox, select, radio button or other form input control.
.callout.is-important
header Angular Forms Does Not Require A Style Library
:markdown
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
the styles of any external library. We are welcome to use the CSS library we choose
... or none at all.
Heres an example of using the **ng-control** and **ng-model** directives as well as handling the **ng-model** event on a textbox:
```
<input type="text"
ng-control="alterEgo"
[ng-model]="model.alterEgo"
(ng-model)="model.alterEgo=$event"
required>
```
In this example were using the standard Angular 2 [property-name] binding syntax to bind the **ng-model** directive to the model objects **alterEgo** property. Were also listening for an event named **ng-model** using the standard (event-name) event binding syntax. When the **ng-model** event fires, the value of the input control is passed using the **$event** object and assigned to the **model.alterEgo** property. This pattern of listening for property changes through an event provides an efficient way to *emulate* the two-way data binding mechanism that Angular 1.x provided while allowing Angular 2 to provide several performance benefits.
.l-main-section
:markdown
## Add Powers with ***ng-for**
Our hero may choose one super power from a fixed list of Agency-approved powers.
We maintain that list internally (in `HeroFormComponent`).
In scenarios where the **ng-model** event handler must perform additional work aside from simply updating the property, we can define it separately using the standard (event-name) syntax and then bind it to a component event handler function:
```
<input type="text"
ng-control="alterEgo"
[ng-model]="model.alterEgo"
(ng-model)="myEventHandler($event)"
required>
```
When we use **[ng-model]** and **(ng-model)** it may seem strange that the directive and event are named the same. Why wouldnt they have different names to make it easier to distinguish them from one another in the HTML template?
We'll add a `select` to our
form and bind the options to the `powers` list using `NgFor`,
a technique we might have seen before in the ["Displaying Data"](./displaying-data.html) chapter.
The answer is that Angular 2 provides an alternative syntax that merges the **ng-model** directive and event concepts together into one attribute on the input element which reduces the amount of code that has to be written. This merging of the property binding and event binding syntax can be accomplished using the **[(ng-model)]** short-cut syntax.
Add the following HTML *immediately below* the "Alter Ego" group.
+makeExample('forms/ts/src/app/hero-form.component.html', 'powers')
The [property-name] portion of the syntax handles the property binding while the (event-name) portion handles the event binding. The directive automatically updates the target property as the **ng-model** event fires allowing us to eliminate the **(ng-model)=”model.alterEgo=$event”** syntax shown earlier.
:markdown
We are repeating the `<options>` tag for each power in the list of Powers.
The `#p` local template variable is a different power in each iteration;
we display its name using the interpolation syntax with the double-curly-braces.
Heres an example of using the more concise **[(ng-model)]** syntax to simplify the **ng-model** code shown earlier:
```
<input type="text"
ng-control="alterEgo"
[(ng-model)]="model.alterEgo"
required>
```
The **[(ng-model)]** code accomplishes the same task as the previous code that defined the directive and event separately. By using the shortcut binding syntax were able to handle property and event bindings in a single step.
Its important to note that this shortcut syntax is optional and not something that were required to use. Just as the **[property-name]** shortcut syntax can be replaced with **bind-property-name** and **(event-name)** can be replaced with **on-event**-name (referred to as the “canonical syntax”) in Angular 2 templates, we can also use a canonical syntax in place of the **[(ng-model)]** syntax if desired. The canonical style uses the **bindon-property-name** syntax in place of the **[(ng-model)]** shortcut syntax:
```
<input type="text"
ng-control="alterEgo"
bindon-ng-model="model.alterEgo"
required>
```
Angular 2 allows us to choose the syntax that we prefer (shortcut or canonical) with all template bindings which makes it flexible to work with and also allows teams to decide on their own unique coding style.
## Disabling the Submit Button
The final step in the template-driven form that weve been building throughout this chapter involves disabling the submit button when the form is invalid. After all, why would we want the user to be able to click the button when we know the form isnt ready to process yet?
Earlier we defined the form using the following syntax:
```
<form (ng-submit)="onSubmit()" #f="form">
```
The **#f** attribute creates a template local variable that can be used to access the form object elsewhere in the template. By checking the forms **valid** property we can easily determine if the form is valid or not. By using **f.form.valid** we can disable the submit button by binding to the **disabled** property:
```
<button type="submit"
class="btn btn-default"
[disabled]="!f.form.valid">
Submit
</button>
```
## Add Custom CSS to Provide Visual Feedback
Earlier in the chapter we saw that form controls can be modified to show the user when a given control is valid or invalid as they interact with the form. For example, when a form first loads we can mark required controls with a red border if no data is present to provide the user with a visual indicator:
.l-main-section
:markdown
## Two-way data binding with ***ng-model**
We might be disappointed if we ran the app right now.
figure.image-display
img(src="/resources/images/devguide/forms/tdf-3.png" alt="Invalid Form")
img(src="/resources/images/devguide/forms/hf-3.png" alt="Early form with no binding")
:markdown
We quickly realize that we are not binding to the `Hero` yet.
We know how to do that from earlier chapters.
We learned show data on screen with a Property Binding in "[Displaying Data](./displaying-data.html)".
We learned to listen for DOM events with an
Event Binding and how to extract values from the screen
in "[User Input](./user-input.html)".
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` directive, that
makes binding our form to the model super-easy.
Find the `<input>` tag for the "Name" and update it like this
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-1')
.l-sub-section
:markdown
We appended a diagnostic interpolation after the input tag
so we can see what we're doing.
We left ourselves a note to throw it way when we're done.
:markdown
As the user interacts with the form and satisfies a controls requirements we can change from a red border to a green border to let them “visually” know how theyre doing:
Focus on the binding syntax: `[(ng-model)]="..."`.
If we ran the app right now and started typing in the "Name" input box,
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" alt="ng-model in action")
:markdown
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!**
Let's add similar `[(ng-model)]` bindings 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.
Then we can confirm that we are in fact two-way data binding *to the entire Hero model*.
After revision the core of our form should have three `[(ng-model)]` bindings that
look much like this:
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-2')
:markdown
If we ran the app right now and made a bunch of changes at some point it might look like this.
figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" alt="ng-model in super action")
:markdown
We've changed every Hero model property and the diagnostic near the top of the form
confirms that our changes are reflected in the model.
** We're done with the diagnostic binding. Delete it now.**
.alert.is-helpful
:markdown
Although `NgModel` is officially a "Forms" directive we can use `[(ng-model)]` and two-way binding outside of forms too.
:markdown
## Inside [(ng-model)]
Do we *really want* to know? If we're just happy that it works, move on to the next topic in this chapter.
Otherwise, stick around for this note.
.l-sub-section
:markdown
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on.
We write a Property Binding to flow data 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**.
We write an Event Binding to flow data 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/src/app/hero-form.component.html', 'ng-model-3')
:markdown
<br>The Property Binding should feel familiar. The Event Binding might seem strange.
The name `ng-model-change` is not an event we recognize.
It is a real event property ... of the `NgModel` directive.
When Angular sees a binding target in the form <span style="font-family:courier">[(abc)]</span>,
it expects the `abc` directive to have an `abc` input property and an `abc-change` 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 `ng-model-change` property doesn't produce a DOM event; it's an Angular `EventEmitter`
property that returns the input box value when it fires ... which is precisely what
we should assign to the model's `name' property.
Nice to know but is it practical? We'd always prefer the `[(ng-model)]`.
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) chapter.
.l-main-section
:markdown
## Track change-state and validity with **ng-control**
A form isn't just about data binding. We'd also like to know the state of the controls on our form.
The `NgControl` directive keeps track of control state for us.
.callout.is-helpful
header NgControl requires Form
:markdown
The `NgControl` is one of a family of `NgForm` directives that can only be applied to
a control within a `<form`> tag.
:markdown
Our application can ask an `NgControl` instance if
* the user touched the control (`ng-touched` | `ng-untouched`)
* the value changed (`ng-pristine` | `ng-dirty`)
* is the value is valid (`ng-valid` | `ng-invalid`)
`NgControl` doesn't just track state; it updates the control with special
Angular CSS classes from the set we listed above.
We can leverage those class names to change the appearance of the
control and make messages appear or disappear.
We'll explore those effects soon. Right now
we should **add `ng-control`to all three of our form controls**,
starting with the "Name" input box
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-1')
:markdown
Be sure to assign a unique name to each `ng-control` directive.
.l-sub-section
:markdown
Angular registers controls under their `ng-control` names
with the `NgForm`.
We didn't add the `NgForm` directive explicitly but it's here
and we'll talk it [later in this chapter](#ng-form).
.l-main-section
:markdown
## Add Custom CSS for Visual Feedback
`NgControl` doesn't just track state. It updates the control with three classes, one
each from the following pairs of Angular form CSS classes.
* control visited: (`ng-touched` | `ng-untouched`)
* value changed: (`ng-pristine` | `ng-dirty`)
* validity: (`ng-valid` | `ng-invalid`)
Let's add a temporary [local template variable](./template-syntax.html#local-vars) named **spy**
to the "Name" `<input>` tag and use the spy to display those classes with an interpolation binding.
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-2')
:markdown
If we ran the app, focused our attention on the "Name" input box, and followed the next four steps *precisely*
1. Look but don't touched
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
... we would see the following four sets of class names and their transitions:
figure.image-display
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" alt="Invalid Form")
:markdown
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.
We realize we can do both at the same time with a colored bar on the left of the input box:
figure.image-display
img(src="/resources/images/devguide/forms/tdf-4.png" alt="Complete Valid Form")
img(src="/resources/images/devguide/forms/validity-required-indicator.png" alt="Invalid Form")
:markdown
Before discussing how to accomplish this task, lets discuss features that form controls expose when they have **ng-control** or **ng-model** directives applied to them. When a control has either of these directives the control provides access to properties such as **pristine, dirty, touched, untouched** and **valid**. As these properties change, CSS classes are added to the control automatically by the aforementioned directives. We can use the CSS classes that are added to dynamically show visual feedback to the end user.
Lets walk through the CSS classes that change as the user interacts with the **name** control in the form. When the form first loads the **Hero** model data is bound into the control. The following CSS classes appear on the input control due to the control having an initial value:
<img src="/resources/images/devguide/forms/name-control-1.png" alt="CSS class initial value">
We achieve this effect by adding two CSS styles to our `styles.css` file
that select for the Angular validity classes and the HTML 5 "required" attribute:
+makeExample('forms/ts/src/styles.css')
:markdown
The key CSS classes added to the control in this example include **ng-pristine, ng-valid** and **ng-untouched**. The presence of the **ng-pristine** class lets us know that the user hasnt interacted with the control yet, the **ng-valid** class appears because the control is valid and **ng-untouched** appears because the user hasnt interacted with the control yet.
## Show and Hide Validation Error messages
If the user clears out the value of the **name** control the CSS classes change to the following:
We can do better.
<img src="/resources/images/devguide/forms/name-control-2.png" alt="CSS class initial value">
In this case the **ng-touched, ng-dirty** and **ng-invalid** classes have been added. The presence of the **ng-touched** class lets us know that the user has interacted with the control, **ng-dirty** lets us know that the controls value has changed and **ng-invalid** lets us know that the control isnt currently valid.
Now that we know the classes that are added to the control we can modify our sites CSS stylesheet and handle valid and invalid control states to provide visual feedback in the form for the end user. For example, when the control is valid we can add the following CSS class to change the controls left-border to green:
```
.ng-valid {
border-left: 5px solid #42A948;
}
```
When the control is invalid we can add the following CSS class to change the left-border to red:
```
.ng-invalid {
border-left: 5px solid #a94442;
}
```
By using the CSS classes automatically added by **ng-control** and **ng-model** we can easily customize a form and display different visual feedback as input controls are modified by the end user.
## Show and Hide Validation Error Messages
All of the controls in the form have the HTML5 **required** attribute applied to them. While weve taken care of disabling the submit button when the form isnt valid, we need a way to display an error message to the end user for each invalid control.
To do this we first need to create a local variable on the control and assign it to a value of form. Heres an example of creating two local variables that are named **#name** and **#alterego**:
```
<input type="text" class="form-control" ng-control="name" #name="form"
[(ng-model)]="model.name" required>
<input type="text" class="form-control" ng-control="alterEgo" #alterego="form"
[(ng-model)]="model.alterEgo" required>
```
Once each input has a local variable assigned we can access the variable elsewhere in the template to determine if the control is valid or not. If a control is invalid an error message can be shown to the end user. How does this work exactly?
Since each input has a local variable defined we can use it to access a **control** property. This property allows us to get to information about the control including whether or not its valid. Heres an example of using the **#name** local variable to access the **valid** property:
code-example(language="html").
Valid: {{ name.valid }}
:markdown
In this example were accessing the desired form control through the **#name** local variable. It exposes properties such as **valid, pristine, dirty** and **touched**. Heres an example of binding the **hidden** property of multiple div elements that contain an error message to the **valid** property of a form control:
```
<label for="name">Name</label>
<input type="text" class="form-control" ng-control="name" #name="form"
[(ng-model)]="model.name" required>
<div class="alert alert-danger" [hidden]="name.valid">
Name is required
</div>
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" ng-control="alterEgo" #alterego="form"
[(ng-model)]="model.alterEgo" required>
<div class="alert alert-danger" [hidden]="alterego.valid">
Alter Ego is required
</div>
```
If an end user clears out the **name** and **alterego** control values the appropriate error messages will be shown:
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.
Here's the way it should look when the user deletes the name:
figure.image-display
img(src="/resources/images/devguide/forms/tdf-5.png" alt="Invalid Data Form")
img(src="/resources/images/devguide/forms/name-required-error.png" alt="Name required")
:markdown
As values are supplied and the controls are valid again, the error messages will automatically be hidden.
To achieve this effect we extend the `<input>` tag with
1. a [local template variable](./template-syntax.html#local-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/src/app/hero-form.component.html', 'name-with-error-msg')
:markdown
We initialized the template local variable with the word "form" (`#name="form"`)
Angular recognizes that syntax and sets the `name` varable
to the `Control` object identified by the `ng-control` directive which,
not coincidentally, we called "name".
We bind the `Control` object's `valid` property to the element's `hidden` property.
While the control is valid, the message is hidden;
if it becomes invalid, the message is revealed.
<a id="ng-form"></a>
.l-sub-section
:markdown
Recall from the previous section that `ng-control` registered this input box with the
`NgForm` directive as "name".
We didn't add the **[`NgForm`](../api/core/NgForm-class.html) directive** explicitly.
Angular added it surreptiously, wrapping it around the `<form>` element when we
told the `HeroFormComponent` to use the `FORM_DIRECTIVES` like this
+makeExample('forms/ts/src/app/hero-form.component.ts', 'directives')
<br>
:markdown
The `NgForm` directive supplements the the `form` element with additional features.
It collects `Controls` (elements identified by an `ng-control` directive)
and monitors their properties including their validity.
It has its own `valid` property which is true only if every contained
control is valid.
In this example, we are pulling the "name" control out of its `controls` collection
and assigning it to the template local variable so that we can
access the control's properties ... such as the control's own `valid` property.
:markdown
The "AlterEgo" is optional so we can leave that be.
"Power" selection is required.
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.
.l-main-section
:markdown
## Submit the form with **ng-submit**
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
trigger a form submit because of its type (`type="submit"`).
A "form submit" is meaningless at the moment.
We'll update the `<form>` tag with another Angular directive, `NgSubmit`,
and bind it to our `HeroFormComponent.submit()` method with an EventBinding
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-submit')
:markdown
We slipped in something extra there at the end! We defined a
template local variable, **`#hf`**, and initialized it with the value, "form".
The variable `hf` is now a handle to the `NgForm` as we [discussed earlier](#ng-form)
with respect to `ng-control` although this time we have a reference to the form
rather than a control.
We'll bind the Form's over-all validity via
the `hf` variable to the button's `disabled` property
using an Event Binding. Here's the code:
+makeExample('forms/ts/src/app/hero-form.component.html', 'submit-button')
:markdown
If we run the application now, we find that the button is enabled.
It doesn't do anything useful yet but it's alive.
Now if we delete the "Name", we violate the "required" rule which
is duely noted in our error message.
Check the "Submit" button. It should be 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 local variable on the (enhanced) form element
2. Reference that variable in a button some 50 lines away.
.l-main-section
:markdown
## Toggle two form regions (Extra Credit)
Submitting the form isn't terribly dramatic at the moment.
.l-sub-section
:markdown
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 we're not interested, we can skip to the chapter's conclusion
and not miss a thing.
:markdown
Let's do something more strikingly visual.
Let's hide the data entry area and display something else.
Start by wrapping the form in a `<div>` and binding
its `hidden` property to the `HeroFormComponent.submitted` property.
+makeExample('forms/ts/src/app/hero-form.component.html', 'edit-div')
:markdown
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:
+makeExample('forms/ts/src/app/hero-form.component.ts', 'submitted')
:markdown
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/src/app/hero-form.component.html', 'submitted')
:markdown
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.
There's an "Edit" button whose click event we bound to an expression
that clears the `submitted` flag.
Click it and this block disappears and the editable form reappears.
That's as much drama as we can muster for now.
.l-main-section
:markdown
## Conclusion
The Angular 2 form discussed in this chapter takes advantage of the following framework features to provide support for data modification, validation and more:
- An HTML form template.
- A form component class with **Component** and **View** annotations.
- The **ng-submit** event for handling the form submission.
- Template local variables such as **#f, #name, #alterego** and **#power**.
- The **ng-control** and **ng-model** directives.
- The **[(ng-model)]** shortcut syntax to bind the directive to a model object property and handle property updates as data changes in the form.
- HTML5 validation attributes such as **required**.
- The local variables **control** property on input controls to check if a control is valid and show/hide error messages.
- Angular 2 property binding shortcut syntax to disable the submit button when the form is invalid.
- Custom CSS classes that provide visual feedback to end users about valid and invalid controls.
- An Angular HTML form template.
- A form component class with a `Component` decorator.
- The `ng-submit` directive for handling the form submission.
- Template local variables such as `#hf`, `#name`, `#alter-ego` and `#power`.
- The `ng-model` directive for two-way data binding.
- The `ng-control` for validation and form element change tracking.
- The local variables `valid` property on input controls to check if a control is valid and show/hide error messages.
- Property Binding to disable the submit button when the form is invalid.
- Custom CSS classes that provide visual feedback to users about required invalid controls.
Heres the final version of the form template that includes all of these framework features:
Heres the final version of the application includes all of these framework features:
```
<div class="container">
<div [hidden]="submitted">
<h1>Template Driven Form</h1>
<form (ng-submit)="onSubmit()" #f="form">
<div class="form-group">
<label for="name">Name</label>
<input type="text" class="form-control" ng-control="name" #name="form"
[(ng-model)]="model.name" required>
<div class="alert alert-danger" [hidden]="name.valid">
Name is required
</div>
</div>
<div class="form-group">
<label for="alterEgo">Alter Ego</label>
<input type="text" class="form-control" ng-control="alterEgo"
#alterego="form" [(ng-model)]="model.alterEgo" required>
<div class="alert alert-danger" [hidden]="alterego.valid">
Alter Ego is required
</div>
</div>
<div class="form-group">
<label for="power">Hero Power</label>
<select class="form-control" ng-control="power" #power="form"
[(ng-model)]="model.power" required>
<option *ng-for="#p of powers" [value]="p">{{p}}</option>
</select>
<div class="alert alert-danger" [hidden]="power.valid">
Power is required
</div>
</div>
<button type="submit" class="btn btn-default"
[disabled]="!f.form.valid">Submit</button>
</form>
</div>
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-md-2">Name</div>
<div class="col-md-10 pull-left">{{ model.name }}</div>
</div>
<div class="row">
<div class="col-md-2">Alter Ego</div>
<div class="col-md-10 pull-left">{{ model.alterEgo }}</div>
</div>
<div class="row">
<div class="col-md-2">Power</div>
<div class="col-md-10 pull-left">{{ model.power }}</div>
</div>
<br />
<button class="btn btn-default" (click)="submitted=false">Edit</button>
</div>
</div>
```
Heres our final template-driven form component code:
code-example(format="linenums").
import {
NgFor,
Component,
View,
FORM_DIRECTIVES,
} from 'angular2/angular2';
import { Hero } from './hero';
@Component({
selector: 'template-driven-form',
templateUrl: 'app/template-form.component.html',
directives: [FORM_DIRECTIVES, NgFor]
})
export class TemplateFormComponent {
model: Hero;
powers: string[];
submitted: boolean = false;
constructor() {
this.model = new Hero(18, 'Dr IQ', 'Really Smart', 'Chuck Overstreet');
this.powers = ['Really Smart', 'Super Flexible',
'Super Hot', 'Weather Changer'];
}
onSubmit() {
this.submitted = true;
}
}
+makeTabs('forms/ts/src/app/hero-form.component.html, '+
'forms/ts/src/app/hero-form.component.ts , '+
'forms/ts/src/app/hero.ts',
'final, final,',
'hero-form.component.html, hero-form.component.ts, hero.ts')
:markdown

View File

@ -215,7 +215,7 @@ table
Thats “HTML Plus”.
Now we are learning about data binding. At first glance it appears that we are setting attributes,
as when we bind button `disabled` to the components `isUnchanged` data property:
as when we bind button `disabled` to the components `isUnchanged` property:
```
<button [disabled]="isUnchanged">Save</button>
```
@ -225,27 +225,52 @@ table
Our intuition is wrong! Our everyday HTML mental model is misleading us.
In fact, once we start data binding, we are no longer working with HTML *attributes*. We aren't setting attributes.
We are setting the *properties* of elements, components, and directives.
We are setting the *properties* of DOM elements, Components, and Directives.
The disabled button example may help make this distinction clear.
If we were trying to disable a button in static HTML, we would add the `disabled` attribute to it.
To enable the button, we'd omit or remove the `disabled` attribtue.
```
<button>Enabled</button>
<button disabled>Disabled</button>
```
We'd get a pretty surprising result if we tried to set the `disabled` attribute.
```
<button disabled=false>Still disabled<button>
```
Say what!?!? Setting the `disabled` attribute to false does not enable the button.
In fact, any mention of the `disabled` attribute disables the button, regardless of the value.
.l-sub-section
:markdown
### HTML Attribute vs. DOM Property
On the other hand, setting the button's `disabled` *property* via a data binding does exactly what we expect.
```
<button [disabled]="true">Disabled</button>
<button [disabled]="false">Enabled</button>
```
The distinction between an HTML attribute and a DOM property is crucial to understanding how Angular binding works.
**Attributes are defined by HTML. Properties are defined by DOM (the Document Object Model).**
* A few HTML attributes have 1:1 mapping to properties. `id` is one example.
* Some HTML attributes don't have corresponding properties. `colspan` is one example.
* Some DOM properties don't have corresponding attributes. `textContent` is one example.
* Many HTML attributes appear to map to properties ... but not the way we think!
That last category can be especially confusing ... until we understand this general rule:
**Attributes *initialize* DOM properties and then they are done.
Property values may change; attribute values don't.**
For example, when the browser renders `<input type="text" value="Bob">`, it creates a
corresponding DOM node with a `value` property *initialized* to "Bob".
When the user enters "Sally" into the input box, the DOM element `value` *property* becomes "Sally".
But the HTML `value` *attribute* remains unchanged as we discover if we ask the input element
about that attribute: `input.getAttribute('value') // returns "Bob"`
The HTML attribute `value` specifies the *initial* value; the DOM `value` property is the *current* value.
The `disabled` attribute is another peculiar example. A button's `disabled` *property* is
`false` by default so the button is enabled.
When we add the `disabled` *attribute*, it's presence alone initializes the button's `disabled` *property* to `true`
so the button is disabled.
Adding and removing the `disabled` *attribute* disables and enables the button. The value of the *attribute* is irrelevant
which is why we cannot enable a button by writing `<button disabled="false">Still Disabled</button>`.
Setting the button's `disabled` *property* (e.g. with an Angular binding) disables or enables the button.
The value of the *property* matters.
**The HTML attribute and the DOM property are not the same thing even when they have the same name.**
:markdown
This is so important, well say it again.
**Template binding works with *properties* and *events*, not *attributes*.**

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB