(docs) devguide forms improved by looking at DART version

closes #425
This commit is contained in:
Ward Bell 2015-11-30 20:41:09 -08:00
parent 347c79c514
commit f7b2913231
4 changed files with 142 additions and 135 deletions

View File

@ -184,7 +184,7 @@
<input type="text" class="form-control" required <input type="text" class="form-control" required
[(ng-model)]="model.name" [(ng-model)]="model.name"
ng-control="name" #spy > ng-control="name" #spy >
TODO: remove this: {{spy.className}} <br>TODO: remove this: {{spy.className}}
<!-- #enddocregion ng-control-2 --> <!-- #enddocregion ng-control-2 -->
</form> </form>

View File

@ -1,16 +1,13 @@
// #docplaster // #docplaster
// #docregion // #docregion
// #docregion first, final // #docregion first, final
import {Component, CORE_DIRECTIVES, FORM_DIRECTIVES} from 'angular2/angular2'; import {Component} from 'angular2/angular2';
import { Hero } from './hero'; import { Hero } from './hero';
@Component({ @Component({
selector: 'hero-form', selector: 'hero-form',
templateUrl: 'app/hero-form.component.html', templateUrl: 'app/hero-form.component.html'
// #docregion directives
directives: [CORE_DIRECTIVES, FORM_DIRECTIVES]
// #enddocregion
}) })
export class HeroFormComponent { export class HeroFormComponent {

View File

@ -18,29 +18,29 @@ include ../../../../_includes/_util-fns
We will build a simple form from scratch, one step at a time. Along the way we'll learn 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 - 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-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 `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 - 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 display validation errors to users and enable/disable form controls
- how to share information across controls with template local variables - How to share information across controls with template local variables
.l-main-section .l-main-section
:marked :marked
## Template-Driven Forms ## Template-Driven Forms
Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with Many of us will build forms by writing templates in the Angular [template syntax](./template-syntax.html) with
the form-specific Directives and techniques described in this chapter. the form-specific directives and techniques described in this chapter.
.l-sub-section .l-sub-section
:marked :marked
That's not the only way to create a form but it's the way we'll cover in this chapter. That's not the only way to create a form but it's the way we'll cover in this chapter.
:marked :marked
We can build almost any form we need with an Angular template ... login forms, contact forms ... pretty much any business forms. We can build almost any form we need with an Angular template $mdash; login forms, contact forms ... pretty much any business forms.
We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors, We can lay out the controls creatively, bind them to data, specify validation rules and display validation errors,
conditionally enable or disable specific controls, trigger built-in visual feedback, and much more. conditionally enable or disable specific controls, trigger built-in visual feedback, and much more.
@ -50,7 +50,7 @@ include ../../../../_includes/_util-fns
We'll discuss and learn to build the following template-driven form: We'll discuss and learn to build the following template-driven form:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/hf-1.png" alt="Clean Form") img(src="/resources/images/devguide/forms/hf-1.png" width="400px" alt="Clean Form")
:marked :marked
Here at the *Hero Employment Agency* we use this form to maintain personal information about the Here at the *Hero Employment Agency* we use this form to maintain personal information about the
@ -61,7 +61,7 @@ figure.image-display
If we delete the hero name, the form displays a validation error in an attention grabbing style: If we delete the hero name, the form displays a validation error in an attention grabbing style:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/hf-2.png" alt="Invalid, Name Required") img(src="/resources/images/devguide/forms/hf-2.png" width="400px" alt="Invalid, Name Required")
:marked :marked
Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red. Note that the submit button is disabled and the "required" bar to the left of the input control changed from green to red.
@ -95,20 +95,23 @@ figure.image-display
That describes well our `Hero` class with its three required fields (`id`, `name`, `power`) That describes well our `Hero` class with its three required fields (`id`, `name`, `power`)
and one optional field (`alterEgo`). and one optional field (`alterEgo`).
Create a new file called `hero.ts` and give it the following class definition: Create a new file in the app folder called `hero.ts` and give it the following class definition:
+makeExample('forms/ts/src/app/hero.ts') +makeExample('forms/ts/src/app/hero.ts', null, 'hero.ts')
:marked :marked
It's an anemic model with few requirements and no behavior. Perfect for our demo. It's an anemic model with few requirements and no behavior. Perfect for our demo.
The TypeScript compiler generates a public field for each `public` constructor parameter and The TypeScript compiler generates a public field for each `public` constructor parameter and
assigns the parameters value to that field automatically when we create new heroes like this: assigns the parameters value to that field automatically when we create new heroes.
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
We can create a new hero like this:
``` ```
let myHero = new Hero(42, 'SkyDog', 'Fetch any object at any distance', 'Leslie Rollover'); 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" console.log('My hero is called ' + myHero.name); // "My hero is called SkyDog"
``` ```
The `alterEgo` is optional and the constructor lets us omit it; note the (?) in `alterEgo?`.
.l-main-section .l-main-section
:marked :marked
@ -120,17 +123,14 @@ figure.image-display
Create a new file called `hero-form.component.ts` and give it the following definition: Create a new file called `hero-form.component.ts` and give it the following definition:
+makeExample('forms/ts/src/app/hero-form.component.ts', 'first') +makeExample('forms/ts/src/app/hero-form.component.ts', 'first', 'app/hero-form.component.ts (v.1)')
:marked :marked
Theres nothing special about this component, nothing to distinguish it from any component we've written before, Theres nothing special about this component, nothing form-specific, nothing to distinguish it from any component we've written before.
nothing form-specific about it ... except, perhaps, the tell-tale `FORM_DIRECTIVES` import.
Understanding this component requires only the Angular 2 concepts weve learned in previous chapters Understanding this component requires only the Angular 2 concepts weve learned in previous chapters
1. We import a standard set of symbols from the Angular library. 1. We import the `Component` decorator from the Angular library as we usually do.
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
1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag. 1. The `@Component` selector value of "hero-form" means we can drop this form in a parent template with a `<hero-form>` tag.
@ -144,15 +144,17 @@ figure.image-display
1. We threw in a `diagnostic` property at the end to return a JSON representation of our model. 1. We threw in a `diagnostic` property at the end to return a JSON representation of our model.
It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later. It'll help us see what we're doing during our development; we've left ourselves a cleanup note to discard it later.
We may wonder why we aren't writing the template inline in the component file as we have often done Why don't we write the template inline in the component file as we often do
elsewhere in the Developer Guide. elsewhere in the Developer Guide?
There is no “right” answer for all occasions. We kind of like inline templates when they are short. There is no “right” answer for all occasions. We 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 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. 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. We also like short files with a clear and obvious purpose like this one.
We made a good choice to put the HTML template elsewhere. Let's write it. We made a good choice to put the HTML template elsewhere.
We'll write that template in a moment. Before we do, we'll take a step back
and revise the `app.ts` to make use of our new `HeroFormComponent`.
.l-main-section .l-main-section
:marked :marked
@ -161,7 +163,7 @@ figure.image-display
`app.ts` is the application's root component. It will host our new `HeroFormComponent`. `app.ts` is the application's root component. It will host our new `HeroFormComponent`.
Replace the contents of the "QuickStart" version with the following: Replace the contents of the "QuickStart" version with the following:
+makeExample('forms/ts/src/app/app.ts') +makeExample('forms/ts/src/app/app.ts', null, 'app.ts')
:marked :marked
.l-sub-section .l-sub-section
@ -181,43 +183,41 @@ figure.image-display
Create a new template file called `hero-form.component.html` and give it the following definition: 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') +makeExample('forms/ts/src/app/hero-form.component.html', 'start', 'app/hero-form.component.html (v.1)')
:marked :marked
That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and That is plain old HTML 5. We're presenting two of the `Hero` fields, `name` and `alterEgo`, and
opening them up for user input in input boxes. opening them up for user input in input boxes.
The "Name" `<input>` control has the HTML5 `required` attribute; The *Name* `<input>` control has the HTML5 `required` attribute;
the "Alter Ego" `<input>` control does not because `alterEgo` is optional. the *Alter Ego* `<input>` control does not because `alterEgo` is optional.
We've got a "Submit" button at the bottom with some classes on it. We've got a *Submit* button at the bottom with some classes on it.
**We are not using Angular yet**. There are no bindings. No extra directives. Just layout. **We are not using Angular yet**. There are no bindings. No extra directives. Just layout.
The `container`,`form-group`, `form-control`, and `btn` classes are CSS Bootstrap. Purely cosmetic. The `container`,`form-group`, `form-control`, and `btn` classes
come from [Twitter Boostrap](http://getbootstrap.com/css/). Purely cosmetic.
We're using Bootstrap to gussy up our form. We're using Bootstrap to gussy up our form.
Hey, what's a form without a little style! Hey, what's a form without a little style!
.l-sub-section
:marked
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.
Open a terminal window and enter the command:
code-example(language="html" escape="html").
npm install bootstrap
:marked
<br>Open the `index.html` and add the following line wherever we like to put our CSS
+makeExample('forms/ts/src/index.html', 'bootstrap')(format=".")
.callout.is-important .callout.is-important
header Angular Forms Does Not Require A Style Library header Angular Forms Do Not Require A Style Library
:marked :marked
Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or Angular makes no use of the `container`, `form-group`, `form-control`, and `btn` classes or
the styles of any external library. We are welcome to use the CSS library we choose the styles of any external library. Angular apps can use any CSS library
... or none at all. ... or none at all.
:marked
Let's add the stylesheet.
ol
li Open a terminal window and enter the command:
code-example(language="html" escape="html").
npm install bootstrap --save
li Open <code>index.html</code> and add the following link to the <code>&lt;head></code>.
+makeExample('forms/ts/src/index.html', 'bootstrap')(format=".")
:marked
.l-main-section .l-main-section
:marked :marked
## Add Powers with ***ng-for** ## Add Powers with ***ng-for**
@ -226,10 +226,10 @@ figure.image-display
We'll add a `select` to our We'll add a `select` to our
form and bind the options to the `powers` list using `NgFor`, form and bind the options to the `powers` list using `NgFor`,
a technique we might have seen before in the ["Displaying Data"](./displaying-data.html) chapter. a technique we might have seen before in the [Displaying Data](./displaying-data.html) chapter.
Add the following HTML *immediately below* the "Alter Ego" group. Add the following HTML *immediately below* the *Alter Ego* group.
+makeExample('forms/ts/src/app/hero-form.component.html', 'powers') +makeExample('forms/ts/src/app/hero-form.component.html', 'powers', 'app/hero-form.component.html (excerpt)')
:marked :marked
We are repeating the `<options>` tag for each power in the list of Powers. We are repeating the `<options>` tag for each power in the list of Powers.
@ -239,17 +239,16 @@ figure.image-display
.l-main-section .l-main-section
:marked :marked
## Two-way data binding with ***ng-model** ## Two-way data binding with ***ng-model**
We might be disappointed if we ran the app right now. Running the app right now would be disappointing.
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/hf-3.png" alt="Early form with no binding") img(src="/resources/images/devguide/forms/hf-3.png" width="400px" alt="Early form with no binding")
:marked :marked
We quickly realize that we are not binding to the `Hero` yet. We don't see hero data because we are not binding to the `Hero` yet.
We know how to do that from earlier chapters. 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)". [Displaying Data](./displaying-data.html) taught us Property Binding.
We learned to listen for DOM events with an [User Input](./user-input.html) showed us how to listen for DOM events with an
Event Binding and how to extract values from the screen Event Binding and how to update a component property with the displayed value.
in "[User Input](./user-input.html)".
Now we need to display, listen, and extract at the same time. Now we need to display, listen, and extract at the same time.
@ -270,54 +269,48 @@ figure.image-display
:marked :marked
Focus on the binding syntax: `[(ng-model)]="..."`. Focus on the binding syntax: `[(ng-model)]="..."`.
If we ran the app right now and started typing in the "Name" input box, 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 adding and deleting characters, we'd see them appearing and disappearing
from the interpolated text. from the interpolated text.
At some point it might look like this. At some point it might look like this.
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action.png" alt="ng-model in action") img(src="/resources/images/devguide/forms/ng-model-in-action.png" width="400px" alt="ng-model in action")
:marked :marked
The diagnostic is evidence that we really are flowing values from the input box to the model and 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!** back again. **That's two-way data binding!**
Let's add similar `[(ng-model)]` bindings to "Alter Ego" and "Hero Power". Let's add similar `[(ng-model)]` bindings to *Alter Ego* and *Hero Power*.
We'll ditch the input box binding message We'll ditch the input box binding message
and add a new binding at the top to the component's `diagnostic` property. and add a new binding at the top to the component's `diagnostic` property.
Then we can confirm that we are in fact two-way data binding *to the entire Hero model*. Then we can confirm that two-way data binding works *for the entire Hero model*.
After revision the core of our form should have three `[(ng-model)]` bindings that After revision the core of our form should have three `[(ng-model)]` bindings that
look much like this: look much like this:
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-2') +makeExample('forms/ts/src/app/hero-form.component.html', 'ng-model-2', 'app/hero-form.component.html (excerpt)')
:marked :marked
If we ran the app right now and made a bunch of changes at some point it might look like this. If we ran the app right now and changed every Hero model property, the form might display like this:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" alt="ng-model in super action") img(src="/resources/images/devguide/forms/ng-model-in-action-2.png" width="400px" alt="ng-model in super action")
:marked :marked
We've changed every Hero model property and the diagnostic near the top of the form The diagnostic near the top of the form
confirms that our changes are reflected in the model. confirms that all of our changes are reflected in the model.
** We're done with the diagnostic binding. Delete it now.** ** We're done with the diagnostic binding. Delete it now.**
.alert.is-helpful
:marked
Although `NgModel` is officially a "Forms" directive we can use `[(ng-model)]` and two-way binding outside of forms too.
:marked
## 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 .l-sub-section
:marked :marked
### Inside [(ng-model)]
*This section is an optional deep dive into [(ng-model)]. Not interested? Skip ahead!*
The punctuation in the binding syntax, <span style="font-family:courier"><b>[()]</b></span>, is a good clue to what's going on. 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. In a Property Binding, a value flows from the model to a target property on screen.
We identify that target property by surrounding its name in brackets, <span style="font-family:courier"><b>[]</b></span>. 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**. 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. In an Event Binding, we flow the value from the target property on screen to the model.
We identify that target property by surrounding its name in parentheses, <span style="font-family:courier"><b>()</b></span>. 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**. This is a one-way data binding in the opposite direction **from the view to the model**.
@ -331,18 +324,18 @@ figure.image-display
:marked :marked
<br>The Property Binding should feel familiar. The Event Binding might seem strange. <br>The Property Binding should feel familiar. The Event Binding might seem strange.
The name `ng-model-change` is not an event we recognize. The `ng-model-change` is not an `<input>` element event.
It is a real event property ... of the `NgModel` directive. It is actually an event property of the `NgModel` directive.
When Angular sees a binding target in the form <span style="font-family:courier">[(abc)]</span>, 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. 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`. The other oddity is the template expression, `model.name = $event`.
We're used to seeing an `$event` object coming from a DOM 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` 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 property that returns the input box value when it fires &mdash; which is precisely what
we should assign to the model's `name' property. we should assign to the model's `name' property.
Nice to know but is it practical? We'd always prefer the `[(ng-model)]`. Nice to know but is it practical? We almost always prefer `[(ng-model)]`.
We might split the binding if we had to do something special in We might split the binding if we had to do something special in
the event handling such as debounce or throttle the key strokes. the event handling such as debounce or throttle the key strokes.
@ -362,10 +355,8 @@ figure.image-display
The `NgControl` is one of a family of `NgForm` directives that can only be applied to The `NgControl` is one of a family of `NgForm` directives that can only be applied to
a control within a `<form`> tag. a control within a `<form`> tag.
:marked :marked
Our application can ask an `NgControl` instance if Our application can ask an `NgControl` if the user touched the control,
* the user touched the control (`ng-touched` | `ng-untouched`) if the value changed, or if the value became invalid.
* 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 `NgControl` doesn't just track state; it updates the control with special
Angular CSS classes from the set we listed above. Angular CSS classes from the set we listed above.
@ -373,9 +364,9 @@ figure.image-display
control and make messages appear or disappear. control and make messages appear or disappear.
We'll explore those effects soon. Right now We'll explore those effects soon. Right now
we should **add `ng-control`to all three of our form controls**, we should **add `ng-control`to all three form controls**,
starting with the "Name" input box starting with the *Name* input box
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-1') +makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-1', 'app/hero-form.component.html (excerpt)')
:marked :marked
Be sure to assign a unique name to each `ng-control` directive. Be sure to assign a unique name to each `ng-control` directive.
@ -384,34 +375,54 @@ figure.image-display
Angular registers controls under their `ng-control` names Angular registers controls under their `ng-control` names
with the `NgForm`. with the `NgForm`.
We didn't add the `NgForm` directive explicitly but it's here We didn't add the `NgForm` directive explicitly but it's here
and we'll talk it [later in this chapter](#ng-form). and we'll talk about it [later in this chapter](#ng-form).
.l-main-section .l-main-section
:marked :marked
## Add Custom CSS for Visual Feedback ## Add Custom CSS for Visual Feedback
`NgControl` doesn't just track state. It updates the control with three classes, one `NgControl` doesn't just track state.
each from the following pairs of Angular form CSS classes. It updates the control with three classes that reflect the state.
* control visited: (`ng-touched` | `ng-untouched`)
* value changed: (`ng-pristine` | `ng-dirty`)
* validity: (`ng-valid` | `ng-invalid`)
table
tr
th State
th Class if true
th Class if false
tr
td Control has been visited
td <code>ng-touched</code>
td <code>ng-untouched</code>
tr
td Control's value has changed
td <code>ng-dirty</code>
td <code>ng-pristine</code>
tr
td Control's value is valid
td <code>ng-valid</code>
td <code>ng-invalid</code>
:marked
Let's add a temporary [local template variable](./template-syntax.html#local-vars) named **spy** 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. to the "Name" `<input>` tag and use the spy to display those classes.
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-2') +makeExample('forms/ts/src/app/hero-form.component.html', 'ng-control-2')
:marked :marked
If we ran the app, focused our attention on the "Name" input box, and followed the next four steps *precisely* Now run the app and focus on the *Name* input box.
Follow the next four steps *precisely*
1. Look but don't touched 1. Look but don't touched
1. Click in the input box, then click outside the text input box 1. Click in the input box, then click outside the text input box
1. Add slashes to the end of the name 1. Add slashes to the end of the name
1. Erase the name 1. Erase the name
... we would see the following four sets of class names and their transitions: The actions and effects are as follows:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" alt="Invalid Form") img(src="/resources/images/devguide/forms/control-state-transitions-anim.gif" alt="Control State Transition")
:marked
We should be able to see the following four sets of class names and their transitions:
figure.image-display
img(src="/resources/images/devguide/forms/ng-control-class-changes.png" width="400px" alt="Control State Transitions")
:marked :marked
The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a The (`ng-valid` | `ng-invalid`) pair are most interesting to us. We want to send a
@ -420,18 +431,18 @@ figure.image-display
We realize we can do both at the same time with a colored bar on the left of the input box: We realize we can do both at the same time with a colored bar on the left of the input box:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/validity-required-indicator.png" alt="Invalid Form") img(src="/resources/images/devguide/forms/validity-required-indicator.png" width="400px" alt="Invalid Form")
:marked :marked
We achieve this effect by adding two styles to a new `styles.css` file We achieve this effect by adding two styles to a new `styles.css` file
that we add to our project as a sibling to `index.html`. that we add to our project as a sibling to `index.html`.
+makeExample('forms/ts/src/styles.css') +makeExample('forms/ts/src/styles.css',null,'styles.css')
:marked :marked
These styles select for the two Angular validity classes and the HTML 5 "required" attribute. These styles select for the two Angular validity classes and the HTML 5 "required" attribute.
We update the `<head>` of the `index.html` to include this style sheet. We update the `<head>` of the `index.html` to include this style sheet.
+makeExample('forms/ts/src/index.html', 'styles')(format=".") +makeExample('forms/ts/src/index.html', 'styles', 'index.html (excerpt)')(format=".")
:marked :marked
## Show and Hide Validation Error messages ## Show and Hide Validation Error messages
@ -443,15 +454,19 @@ figure.image-display
Here's the way it should look when the user deletes the name: Here's the way it should look when the user deletes the name:
figure.image-display figure.image-display
img(src="/resources/images/devguide/forms/name-required-error.png" alt="Name required") img(src="/resources/images/devguide/forms/name-required-error.png" width="400px" alt="Name required")
:marked :marked
To achieve this effect we extend the `<input>` tag with To achieve this effect we extend the `<input>` tag with
1. a [local template variable](./template-syntax.html#local-vars) 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. 1. the "*is required*" message in a nearby `<div>` which we'll display only if the control is invalid.
Here's how we do it for the "name" input box: Here's how we do it for the *name* input box:
+makeExample('forms/ts/src/app/hero-form.component.html', 'name-with-error-msg') -var stylePattern = { otl: /(#name=&quot;form&quot;)|(.*div.*$)|(Name is required)/gm };
+makeExample('forms/ts/src/app/hero-form.component.html',
'name-with-error-msg',
'app/hero-form.component.html (excerpt)',
stylePattern)
:marked :marked
We initialized the template local variable with the word "form" (`#name="form"`) We initialized the template local variable with the word "form" (`#name="form"`)
@ -465,16 +480,13 @@ figure.image-display
<a id="ng-form"></a> <a id="ng-form"></a>
.l-sub-section .l-sub-section
:marked :marked
### The NgForm directive
Recall from the previous section that `ng-control` registered this input box with the Recall from the previous section that `ng-control` registered this input box with the
`NgForm` directive as "name". `NgForm` directive as "name".
We didn't add the **[`NgForm`](../api/core/NgForm-class.html) directive** explicitly. 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 Angular added it surreptiously, wrapping it around the `<form>` element
told the `HeroFormComponent` to use the `FORM_DIRECTIVES` like this
+makeExample('forms/ts/src/app/hero-form.component.ts', 'directives')
<br>
:marked
The `NgForm` directive supplements the `form` element with additional features. The `NgForm` directive supplements the `form` element with additional features.
It collects `Controls` (elements identified by an `ng-control` directive) It collects `Controls` (elements identified by an `ng-control` directive)
and monitors their properties including their validity. and monitors their properties including their validity.
@ -483,11 +495,11 @@ figure.image-display
In this example, we are pulling the "name" control out of its `controls` collection 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 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. access the control's properties &mdash; such as the control's own `valid` property.
:marked :marked
The "AlterEgo" is optional so we can leave that be. The Hero *Alter Ego* is optional so we can leave that be.
"Power" selection is required. Hero *Power* selection is required.
We can add the same kind of error handling to the `<select>` if we want We can add the same kind of error handling to the `<select>` if we want
but it's not imperative because the selection box already constrains the but it's not imperative because the selection box already constrains the
power to valid value. power to valid value.
@ -496,14 +508,14 @@ figure.image-display
:marked :marked
## Submit the form with **ng-submit** ## Submit the form with **ng-submit**
The user should be able to submit this form after filling it in. The user should be able to submit this form after filling it in.
The "Submit" button at the bottom of the form The Submit button at the bottom of the form
does nothing on its own but it will does nothing on its own but it will
trigger a form submit because of its type (`type="submit"`). trigger a form submit because of its type (`type="submit"`).
A "form submit" is meaningless at the moment. A "form submit" is meaningless at the moment.
We'll update the `<form>` tag with another Angular directive, `NgSubmit`, To make it meaningful, we'll update the `<form>` tag with another Angular directive, `NgSubmit`,
and bind it to our `HeroFormComponent.submit()` method with an EventBinding and bind it to our `HeroFormComponent.submit()` method with an EventBinding
+makeExample('forms/ts/src/app/hero-form.component.html', 'ng-submit') +makeExample('forms/ts/src/app/hero-form.component.html', 'ng-submit')(format=".")
:marked :marked
We slipped in something extra there at the end! We defined a We slipped in something extra there at the end! We defined a
@ -521,10 +533,8 @@ figure.image-display
If we run the application now, we find that the button is enabled. If we run the application now, we find that the button is enabled.
It doesn't do anything useful yet but it's alive. It doesn't do anything useful yet but it's alive.
Now if we delete the "Name", we violate the "required" rule which Now if we delete the *Name*, we violate the "required" rule which
is duely noted in our error message. is duely noted in our error message. The Submit button is also disabled.
Check the "Submit" button. It should be disabled.
Not impressed? Think about it for a moment. What would we have to do to Not impressed? Think about it for a moment. What would we have to do to
wire the button's enable/disabled state to the form's validity without Angular's help? wire the button's enable/disabled state to the form's validity without Angular's help?
@ -535,7 +545,7 @@ figure.image-display
.l-main-section .l-main-section
:marked :marked
## Toggle two form regions (Extra Credit) ## Toggle two form regions (extra credit)
Submitting the form isn't terribly dramatic at the moment. Submitting the form isn't terribly dramatic at the moment.
.l-sub-section .l-sub-section
:marked :marked
@ -543,40 +553,40 @@ figure.image-display
jazzing it up won't teach us anything new about forms. jazzing it up won't teach us anything new about forms.
But this is an opportunity to exercise some of our newly won But this is an opportunity to exercise some of our newly won
binding skills. binding skills.
If we're not interested, we can skip to the chapter's conclusion If you're not interested, you can skip to the chapter's conclusion
and not miss a thing. and not miss a thing.
:marked :marked
Let's do something more strikingly visual. Let's do something more strikingly visual.
Let's hide the data entry area and display something else. Let's hide the data entry area and display something else.
Start by wrapping the form in a `<div>` and binding Start by wrapping the form in a `<div>` and bind
its `hidden` property to the `HeroFormComponent.submitted` property. its `hidden` property to the `HeroFormComponent.submitted` property.
+makeExample('forms/ts/src/app/hero-form.component.html', 'edit-div') +makeExample('forms/ts/src/app/hero-form.component.html', 'edit-div', 'app/hero-form.component.html (excerpt)')
:marked :marked
The main form is visible from the start because the The main form is visible from the start because the
the `submitted` property is false until we submit the form the `submitted` property is false until we submit the form,
... as this fragment from the `HeroFormComponent` reminds us: as this fragment from the `HeroFormComponent` reminds us:
+makeExample('forms/ts/src/app/hero-form.component.ts', 'submitted') +makeExample('forms/ts/src/app/hero-form.component.ts', 'submitted')
:marked :marked
When we click the "Submit" button, the `submitted` flag becomes true and the form disappears When we click the Submit button, the `submitted` flag becomes true and the form disappears
as planned. as planned.
Now we need to show something else while the form is in the submitted state. Now 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: Add the following block of HTML below the `<div>` wrapper we just wrote:
+makeExample('forms/ts/src/app/hero-form.component.html', 'submitted') +makeExample('forms/ts/src/app/hero-form.component.html', 'submitted', 'app/hero-form.component.html (excerpt)')
:marked :marked
There's our hero again, displayed read-only with interpolation bindings. There's our hero again, displayed read-only with interpolation bindings.
This slug of HTML only appears while the component is in the submitted state. This 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 We added an Edit button whose click event is bound to an expression
that clears the `submitted` flag. that clears the `submitted` flag.
Click it and this block disappears and the editable form reappears. When we click it, this block disappears and the editable form reappears.
That's as much drama as we can muster for now. That's as much drama as we can muster for now.
@ -596,7 +606,7 @@ figure.image-display
- Property Binding to disable the submit button when the form is invalid. - 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. - Custom CSS classes that provide visual feedback to users about required invalid controls.
Heres the final version of the application includes all of these framework features: Heres the final version of the application:
+makeTabs( +makeTabs(
`forms/ts/src/app/hero-form.component.html, `forms/ts/src/app/hero-form.component.html,

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 KiB