[WIP] docs(user-input): Jessica's user input edits (#2683)

This commit is contained in:
Kapunahele Wong 2016-11-21 15:17:39 -05:00 committed by Ward Bell
parent fc303c0697
commit 694b58d06e
2 changed files with 135 additions and 140 deletions

View File

@ -20,15 +20,14 @@ export class KeyUpComponent_v1 {
// #enddocregion key-up-component-1-class, key-up-component-1-class-no-type
/*
// #docregion key-up-component-1-class-no-type
// without strong typing
onKey(event:any) {
onKey(event:any) { // without type info
this.values += event.target.value + ' | ';
}
// #enddocregion key-up-component-1-class-no-type
*/
// #docregion key-up-component-1-class
// with strong typing
onKey(event: KeyboardEvent) {
onKey(event: KeyboardEvent) { // with type info
this.values += (<HTMLInputElement>event.target).value + ' | ';
}
// #docregion key-up-component-1-class-no-type
@ -53,23 +52,22 @@ export class KeyUpComponent_v2 {
}
// #enddocregion key-up-component-2
//////////////////////////////////////////
// #docregion key-up-component-3
@Component({
selector: 'key-up3',
template: `
<input #box (keyup.enter)="values=box.value">
<p>{{values}}</p>
<input #box (keyup.enter)="onEnter(box.value)">
<p>{{value}}</p>
`
})
export class KeyUpComponent_v3 {
values = '';
value = '';
onEnter(value: string) { this.value = value; }
}
// #enddocregion key-up-component-3
//////////////////////////////////////////
// #docregion key-up-component-4
@ -77,13 +75,14 @@ export class KeyUpComponent_v3 {
selector: 'key-up4',
template: `
<input #box
(keyup.enter)="values=box.value"
(blur)="values=box.value">
(keyup.enter)="update(box.value)"
(blur)="update(box.value)">
<p>{{values}}</p>
<p>{{value}}</p>
`
})
export class KeyUpComponent_v4 {
values = '';
value = '';
update(value: string) { this.value = value; }
}
// #enddocregion key-up-component-4

View File

@ -1,9 +1,9 @@
include ../_util-fns
:marked
When the user clicks a link, pushes a button, or enters text
we want to know about it. These user actions all raise DOM events.
In this chapter we learn to bind to those events using the Angular
User actions such as clicking a link, pushing a button, and entering
text raise DOM events.
This page explains how to bind those events to component event handlers using the Angular
event binding syntax.
Run the <live-example></live-example>.
@ -11,136 +11,154 @@ include ../_util-fns
:marked
## Binding to user input events
We can use [Angular event bindings](./template-syntax.html#event-binding)
to respond to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
You can use [Angular event bindings](./template-syntax.html#event-binding)
to respond to any [DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
Many DOM events are triggered by user input. Binding to these events provides a way to
get input from the user.
The syntax is simple. We surround the DOM event name in parentheses and assign a quoted template statement to it.
As an example, here's an event binding that implements a click handler:
To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted
[template statement](./template-syntax.html#template-statements) to it.
The following example shows an event binding that implements a click handler:
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-button')(format=".", language="html")
<a id="click"></a>
:marked
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
The text within quotes on the right is the **template statement** in which we
respond to the click event by calling the component's `onClickMe` method. A [template statement](./template-syntax.html#template-statements) is a subset
of JavaScript with restrictions and a few added tricks.
The `(click)` to the left of the equals sign identifies the button's click event as the **target of the binding**.
The text in quotes to the right of the equals sign
is the **template statement**, which responds
to the click event by calling the component's `onClickMe` method.
When writing a binding we must be aware of a template statement's **execution context**.
The identifiers appearing within a statement belong to a specific context object.
That object is usually the Angular component that controls the template ... which it definitely is
in this case because that snippet of HTML belongs to the following component:
When writing a binding, be aware of a template statement's **execution context**.
The identifiers in a template statement belong to a specific context object,
usually the Angular component controlling the template.
The example above shows a single line of HTML, but that HTML belongs to a larger component:
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-component', 'app/click-me.component.ts')(format=".")
:marked
When the user clicks the button, Angular calls the component's `onClickMe` method.
When the user clicks the button, Angular calls the `onClickMe` method from `ClickMeComponent`.
.l-main-section
:marked
## Get user input from the $event object
We can bind to all kinds of events. Let's bind to the keyup event of an input box and replay
what the user types back onto the screen.
DOM events carry a payload of information that may be useful to the component.
This section shows how to bind to the `keyup` event of an input box to get the user's input after each keystroke.
This time we'll (1) listen to an event and (2) grab the user's input.
The following code listens to the `keyup` event and passes the entire event payload (`$event`) to the component event handler.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-template', 'app/keyup.components.ts (template v.1)')(format=".")
:marked
Angular makes an event object available in the **`$event`** variable,
which we pass to the component's `onKey()` method.
The user data we want is in that variable somewhere.
When a user presses and releases a key, the `keyup` event occurs, and Angular provides a corresponding
DOM event object in the `$event` variable which this code passes as a parameter to the component's `onKey()` method.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class-no-type', 'app/keyup.components.ts (class v.1)')(format=".")
:marked
The shape of the `$event` object is determined by whatever raises the event.
The `keyup` event comes from the DOM, so `$event` must be a [standard DOM event object](https://developer.mozilla.org/en-US/docs/Web/API/Event).
The `$event.target` gives us an
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement), which
has a `value` property that contains our user input data.
The properties of an `$event` object vary depending on the type of DOM event. For example,
a mouse event includes different information than a input box editing event.
All [standard DOM event objects](https://developer.mozilla.org/en-US/docs/Web/API/Event)
have a `target` property, a reference to the element that raised the event.
In this case, `target` refers to the [`<input>` element](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) and
`event.target.value` returns the current contents of that element.
The `onKey()` component method is where we extract the user's input
from the event object, adding that input to the list of user data that we're accumulating in the component's `values` property.
We then use [interpolation](./template-syntax.html#interpolation)
to display the accumulating `values` property back on screen.
After each call, the `onKey()` method appends the contents of the input box value to the list
in the component's `values` property, followed by a separator character (|).
The [interpolation](./template-syntax.html#interpolation)
displays the accumulating input box changes from the `values` property.
Enter the letters "abc", and then backspace to remove them.
Suppose the user enters the letters "abc", and then backspaces to remove them one by one.
Here's what the UI displays:
code-example().
a | ab | abc | ab | a | |
figure.image-display
img(src='/resources/images/devguide/user-input/keyup1-anim.gif' alt="key up 1")
<a id="keyup1"></a>
.l-sub-section
:marked
We cast the `$event` as an `any` type, which means we've abandoned strong typing
to simplify our code. We generally prefer the strong typing that TypeScript affords.
We can rewrite the method, casting to HTML DOM objects like this.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class', 'app/keyup.components.ts (class v.1 - strongly typed )')(format=".")
:marked
<br>Strong typing reveals a serious problem with passing a DOM event into the method:
too much awareness of template details, too little separation of concerns.
Alternatively, you could accumulate the individual keys themselves by substituting `event.key`
for `event.target.value` in which case the same user input would produce:
code-example().
a | b | c | backspace | backspace | backspace |
We'll address this problem in our next try at processing user keystrokes.
a#keyup1
:marked
### Type the _$event_
The example above casts the `$event` as an `any` type.
That simplifies the code at a cost.
There is no type information
that could reveal properties of the event object and prevent silly mistakes.
The following example rewrites the method with types:
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class', 'app/keyup.components.ts (class v.1 - typed )')(format=".")
:marked
The `$event` is now a specific `KeyboardEvent`.
Not all elements have a `value` property so it casts `target` to an input element.
The `OnKey` method more clearly expresses what it expects from the template and how it interprets the event.
### Passing _$event_ is a dubious practice
Typing the event object reveals a significant objection to passing the entire DOM event into the method:
the component has too much awareness of the template details.
It can't extract information without knowing more than it should about the HTML implementation.
That breaks the separation of concerns between the template (_what the user sees_)
and the component (_how the application processes user data_).
The next section shows how to use template reference variables to address this problem.
.l-main-section
:marked
## Get user input from a template reference variable
There's another way to get the user data without the `$event` variable.
There's another way to get the user data: use Angular
[**template reference variables**](./template-syntax.html#ref-vars).
These variables provide direct access to an element from within the template.
To declare a template reference variable, precede an identifier with a hash (or pound) character (#).
Angular has a syntax feature called [**template reference variables**](./template-syntax.html#ref-vars).
These variables grant us direct access to an element.
We declare a template reference variable by preceding an identifier with a hash/pound character (#).
Here's an example of using a template reference variable
to implement a clever keystroke loopback in an ultra-simple template.
The following example uses a template reference variable
to implement a keystroke loopback in a simple template.
+makeExample('user-input/ts/app/loop-back.component.ts', 'loop-back-component', 'app/loop-back.component.ts')(format=".")
:marked
We've declared a template reference variable named `box` on the `<input>` element.
The `box` variable is a reference to the `<input>` element itself, which means we can
grab the input element's `value` and display it
The template reference variable named `box`, declared on the `<input>` element,
refers to the `<input>` element itself.
The code uses the `box` variable to get the input element's `value` and display it
with interpolation between `<p>` tags.
The template is completely self contained. It doesn't bind to the component,
and the component does nothing.
Type in the input box, and watch the display update with each keystroke. *Voila!*
Type something in the input box, and watch the display update with each keystroke.
figure.image-display
img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back")
.l-sub-section
:marked
**This won't work at all unless we bind to an event**.
**This won't work at all unless you bind to an event**.
Angular only updates the bindings (and therefore the screen)
if we do something in response to asynchronous events such as keystrokes.
That's why we bind the `keyup` event to a statement that does ... well, nothing.
We're binding to the number 0, the shortest statement we can think of.
That is all it takes to keep Angular happy. We said it would be clever!
Angular updates the bindings (and therefore the screen)
only if the app does something in response to asynchronous events, such as keystrokes.
This example code binds the `keyup` event
to the number 0, the shortest template statement possible.
While the statement does nothing useful,
it satisfies Angular's requirement so that Angular will update the screen.
:marked
That template reference variable is intriguing. It's clearly easier to get to the textbox with that
variable than to go through the `$event` object. Maybe we can rewrite our previous
keyup example so that it uses the variable to get the user's input. Let's give it a try.
It's easier to get to the input box with the template reference
variable than to go through the `$event` object. Here's a rewrite of the previous
`keyup` example that uses a template reference variable to get the user's input.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-2' ,'app/keyup.components.ts (v2)')(format=".")
:marked
That sure seems easier.
An especially nice aspect of this approach is that our component code gets clean data values from the view.
A nice aspect of this approach is that the component gets clean data values from the view.
It no longer requires knowledge of the `$event` and its structure.
<a id="key-event"></a>
.l-main-section
:marked
## Key event filtering (with `key.enter`)
Perhaps we don't care about every keystroke.
Maybe we're only interested in the input box value when the user presses Enter, and we'd like to ignore all other keys.
When we bind to the `(keyup)` event, our event handling statement hears *every keystroke*.
We could filter the keys first, examining every `$event.keyCode`, and update the `values` property only if the key is Enter.
Angular can filter the key events for us. Angular has a special syntax for keyboard events.
We can listen for just the Enter key by binding to Angular's `keyup.enter` pseudo-event.
Only then do we update the component's `values` property. (In this example,
the update happens inside the event binding statement. A better practice
would be to put the update code in the component.)
The `(keyup)` event handler hears *every keystroke*.
Sometimes only the _Enter_ key matters, because it signals that the user has finished typing.
One way to reduce the noise would be to examine every `$event.keyCode` and take action only when the key is _Enter_.
There's an easier way: bind to Angular's `keyup.enter` pseudo-event.
Then Angular calls the event handler only when the user presses _Enter_.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-3' ,'app/keyup.components.ts (v3)')(format=".")
:marked
Here's how it works.
figure.image-display
@ -150,76 +168,53 @@ figure.image-display
:marked
## On blur
Our previous example won't transfer the current state of the input box if the user mouses away and clicks
elsewhere on the page. We update the component's `values` property only when the user presses Enter
while the focus is inside the input box.
In the previous example, the current state of the input box
is lost if the user mouses away and clicks elsewhere on the page
without first pressing _Enter_.
The component's `value` property is updated only when the user presses _Enter_.
Let's fix that by listening to the input box's blur event as well.
To fix this issue, listen to both the _Enter_ key and the _blur_ event.
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-4' ,'app/keyup.components.ts (v4)')(format=".")
.l-main-section
:marked
## Put it all together
We learned how to [display data](./displaying-data.html) in the previous chapter.
We've acquired a small arsenal of event binding techniques in this chapter.
The previous page showed how to [display data](./displaying-data.html).
This page demonstrated event binding techniques.
Let's put it all together in a micro-app
that can display a list of heroes and add new heroes to that list.
The user can add a hero by first typing in the input box and then
pressing Enter, clicking the Add button, or clicking elsewhere on the page.
Now, put it all together in a micro-app
that can display a list of heroes and add new heroes to the list.
The user can add a hero by typing the hero's name in the input box and
clicking **Add**.
figure.image-display
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
:marked
Below is the "Little Tour of Heroes" component.
We'll call out the highlights after we bask briefly in its minimalist glory.
+makeExample('user-input/ts/app/little-tour.component.ts', 'little-tour', 'app/little-tour.component.ts')(format=".")
:marked
We've seen almost everything here before. A few things are new or bear repeating.
### Use template variables to refer to elements
### Observations
* **Use template variables to refer to elements** &mdash;
The `newHero` template variable refers to the `<input>` element.
We can use `newHero` from any sibling or child of the `<input>` element.
You can reference `newHero` from any sibling or child of the `<input>` element.
Getting the element from a template variable makes the button click handler
simpler. Without the variable, we'd have to use a fancy CSS selector
to find the input element.
* **Pass values, not elements** &mdash;
Instead of passing the `newHero` into the component's `addHero` method,
get the input box value and pass *that* to `addHero`.
### Pass values, not elements
* **Keep template statements simple** &mdash;
The `(blur)` event is bound to two JavaScript statements.
The first statement calls `addHero`. The second statement, `newHero.value=''`,
clears the input box after a new hero is added to the list.
We could have passed the `newHero` into the component's `addHero` method.
But that would require `addHero` to pick its way through the `<input>` DOM element,
something we learned to dislike in our first try at a [keyup component](#keyup1).
Instead, we grab the input box *value* and pass *that* to `addHero`.
The component knows nothing about HTML or the DOM, which is the way we like it.
### Keep template statements simple
We bound `(blur)` to *two* JavaScript statements.
We like the first one, which calls `addHero`.
We do not like the second one, which assigns an empty string to the input box value.
The second statement exists for a good reason. We have to clear the input box after adding the new hero to the list.
The component has no way to do that itself because it has no access to the
input box (our design choice).
Although the example *works*, we are rightly wary of JavaScript in HTML.
Template statements are powerful. We're supposed to use them responsibly.
Complex JavaScript in HTML is irresponsible.
Should we reconsider our reluctance to pass the input box into the component?
There should be a better third way. And there is, as we'll see when we learn about `NgModel` in the [Forms](forms.html) chapter.
.l-main-section
:marked
## Source code
Here is all the code we talked about in this chapter.
Following is all the code discussed in this page.
+makeTabs(`
user-input/ts/app/click-me.component.ts,
user-input/ts/app/keyup.components.ts,
@ -235,10 +230,11 @@ figure.image-display
:marked
## Summary
We've mastered the basic primitives for responding to user input and gestures.
As powerful as these primitives are, they are a bit clumsy for handling
large amounts of user input. We're operating down at the low level of events when
we should be writing two-way bindings between data entry fields and model properties.
You have mastered the basic primitives for responding to user input and gestures.
Angular has a two-way binding called `NgModel`, which we'll learn about
in the `Forms` chapter.
These techniques are useful for small-scale demonstrations, but they
quickly become verbose and clumsy when handling large amounts of user input.
Two-way data binding is a more elegant and compact way to move
values between data entry fields and model properties.
The next page, `Forms`, explains how to write
two-way bindings with `NgModel`.