(docs) overhaul user-input
This commit is contained in:
parent
0b7b6d0c83
commit
840439770b
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
"user-input": {
|
"user-input": {
|
||||||
"title": "User Input",
|
"title": "User Input",
|
||||||
"intro": "DOM events drive user input in Angular. You can use the native events like click, mouseover, and keyup. Angular uses a special syntax to register events to DOM elements. This section covers all the ins and outs of using the event syntax."
|
"intro": "User input triggers DOM events. We listen to those events with EventBindings that funnel updated values back into our components and models."
|
||||||
},
|
},
|
||||||
|
|
||||||
"pipes": {
|
"pipes": {
|
||||||
|
|
|
@ -30,7 +30,7 @@ include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
>[* and <template>](#star-template)
|
>[* and <template>](#star-template)
|
||||||
|
|
||||||
>[Local variables](#local-vars)
|
>[Local template variables](#local-vars)
|
||||||
|
|
||||||
>[Input and Output Properties](#inputs-outputs)
|
>[Input and Output Properties](#inputs-outputs)
|
||||||
|
|
||||||
|
@ -124,7 +124,7 @@ code-example(format="" language="html" escape="html").
|
||||||
The component itself is usually the expression *context* in which case
|
The component itself is usually the expression *context* in which case
|
||||||
the template expression usually references that component.
|
the template expression usually references that component.
|
||||||
The expression context may include an object other than the component.
|
The expression context may include an object other than the component.
|
||||||
A [template local variable](#local-vars) is one such supplemental context object;
|
A [local template variable](#local-vars) is one such supplemental context object;
|
||||||
we’ll discuss that option below.
|
we’ll discuss that option below.
|
||||||
|
|
||||||
Another is the **`$event`** variable that contains information about an event raised on an element;
|
Another is the **`$event`** variable that contains information about an event raised on an element;
|
||||||
|
|
|
@ -1,183 +1,244 @@
|
||||||
.l-main-section
|
:markdown
|
||||||
h2#section-responding-to-user-input Responding to user input with event syntax
|
When the user clicks a link, pushes a button, or types on the keyboard
|
||||||
|
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 Event Binding syntax.
|
||||||
|
|
||||||
p.
|
:markdown
|
||||||
You can make your application respond to user input by using the event syntax. The event syntax starts with an event name surrounded by parenthesis: <code>(event)</code>. A controller function is then assigned to the event name: <code>(event)="controllerFn()"</code>.
|
## Binding to User Input Events
|
||||||
p.
|
|
||||||
For a particular control like an input you can have it call methods on your controller on keyup event like so:
|
|
||||||
|
|
||||||
code-example(language="html").
|
We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events)
|
||||||
<input (keyup)="myControllerMethod()">
|
with an [Angular Event Binding](./template-syntax.html#event-binding).
|
||||||
|
|
||||||
h3#local-variables Local variables
|
The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses.
|
||||||
p.
|
A click Event Binding makes for a quick illustration.
|
||||||
As in previous examples, you can make element references available to other parts of the template as a local
|
|
||||||
variable using the <code>#var</code> syntax. With this and events, we can do the old "update text as you type" example:
|
code-example(language="html" ).
|
||||||
|
<button (click)="onClickMe()">Click me</button>
|
||||||
code-example(language="html").
|
:markdown
|
||||||
<input #myname (keyup)>
|
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
|
||||||
<p>{{myname.value}}</p>
|
The text within quotes on the right is the "**template expression**" in which we
|
||||||
|
respond to the click event by calling the component's `onClickMe` method. A [template expression](./template-syntax.html#template-expressions) is a subset
|
||||||
p.text-body(ng-non-bindable).
|
of JavaScript with a few added tricks.
|
||||||
The <code>#myname</code> creates a local variable in the template that we'll refer to below in the
|
|
||||||
<code><p></code> element. The <code>(keyup)</code> tells Angular to trigger updates when it gets a keyup
|
When writing a binding we must be aware of a template expression's **execution context**.
|
||||||
event. And the <code>{{myname.value}}</code> binds the text node of the <code><p></code> element to the
|
The identifers appearing within an expression belong to a specific context object.
|
||||||
input's value property.
|
That object is usually the Angular component that controls the template ... which it definitely is
|
||||||
p Let's do something a little more complex where users enter items and add them to a list like this:
|
in this case because that snippet of HTML belongs to the following component:
|
||||||
|
<!--
|
||||||
figure.image-display
|
These sample can be found in http://plnkr.co/edit/mr63T5
|
||||||
img(src='/resources/images/examples/user-input-example1.png' alt="Example of Todo App")
|
-->
|
||||||
|
```
|
||||||
|
@Component({
|
||||||
.l-ain-section
|
selector: 'click-me',
|
||||||
h2#section-create-an-array-property Create an array property
|
template: '<button (click)="onClickMe()">Click me</button>'
|
||||||
p.
|
})
|
||||||
With the default bootstrapping in place, create a controller class that will manage interactions with the
|
class ClickMeComponent {
|
||||||
list. Inside the controller, add an array with an initial list of items. Then add a method that pushes new items
|
onClickMe(){
|
||||||
on the array when called.
|
alert('You are my hero!')
|
||||||
|
}
|
||||||
code-tabs
|
}
|
||||||
code-pane(language="javascript" name="TypeScript" format="linenums").
|
```
|
||||||
//TypeScript
|
The `onClickMe` in the template refers to the `onClickMe` method of the component.
|
||||||
class TodoList {
|
When the user clicks the button, Angular calls the component's `onClickMe` method.
|
||||||
todos: Array<string>;
|
|
||||||
constructor() {
|
|
||||||
this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"];
|
|
||||||
}
|
|
||||||
addTodo(todo: string) {
|
|
||||||
this.todos.push(todo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
code-pane(language="javascript" name="ES5" format="linenums").
|
|
||||||
//ES5
|
|
||||||
function TodoList() {
|
|
||||||
this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"];
|
|
||||||
this.addTodo = function(todo) {
|
|
||||||
this.todos.push(todo);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
.callout.is-helpful
|
|
||||||
header Production Best Practice
|
|
||||||
p.
|
|
||||||
As with the previous example, in a production application you will separate your model out into another class
|
|
||||||
and inject it into <code>TodoList</code>. We've omitted it here for brevity.
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#section-display-the-list-of-todos Display the list of todos
|
:markdown
|
||||||
p.
|
## Get user input from the $event object
|
||||||
Using the <code>*ng-for</code> iterator, create an <code><li></code> for each item in the todos array and set
|
We can bind to all kinds of events. Let's bind to the "keyup" event of an input box and replay
|
||||||
its text to the value.
|
what the user types back onto the screen.
|
||||||
|
|
||||||
|
This time we'll both listen to an event and grab the user's input.
|
||||||
|
code-example(format="linenums" language="html" ).
|
||||||
|
@Component({
|
||||||
|
selector: 'key-up',
|
||||||
|
template: `
|
||||||
|
<h4>Give me some keys!</h4>
|
||||||
|
<div><input (keyup)="onKey($event)"><div>
|
||||||
|
<div>{{values}}</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class KeyUpComponent {
|
||||||
|
values='';
|
||||||
|
onKey(event) {
|
||||||
|
this.values += event.target.value + ' | ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:markdown
|
||||||
|
Angular makes an event object available in the **`$event`** variable. The user data we want is in that variable somewhere.
|
||||||
|
|
||||||
|
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 the
|
||||||
|
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which
|
||||||
|
has a `value` property and that's where we find our user input data.
|
||||||
|
|
||||||
|
With had this in mind when we passed `$event` to our `onKey()` component method. That method extracts the user's input and
|
||||||
|
concatenates it to the previous user data that we're accumulating in the component's' `values` property.
|
||||||
|
We then use [interpolation](./template-syntax.html#interpolation)
|
||||||
|
to display the `values` property back on screen.
|
||||||
|
|
||||||
|
Enter the letters "abcd", backspace to remove them, and we should see:
|
||||||
|
code-example().
|
||||||
|
a | ab | abc | abcd | abc | ab | a | |
|
||||||
|
|
||||||
code-example(language="html" format="linenums").
|
:markdown
|
||||||
<ul>
|
|
||||||
<li *ng-for="#todo of todos">
|
|
||||||
{{ todo }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#section-add-todos-to-the-list Add todos to the list via button click
|
:markdown
|
||||||
p.
|
## Get user input from a local template variable
|
||||||
Now, add a text input and a button for users to add items to the list. As you saw above, you can create a local
|
There's another way to get the user data without the `$event` variable.
|
||||||
variable reference in your template with <code>#varname</code>. Call it <code>#todotext</code> here.
|
|
||||||
|
Angular has syntax feature called [**local template variables**](./template-syntax.html#local-vars).
|
||||||
|
These variables grant us direct access to an element.
|
||||||
|
We declare a local template variable by preceding an identifier with a hash/pound character (#).
|
||||||
|
|
||||||
|
Let's demonstrate with a clever keystroke loopback in a single line of template HTML.
|
||||||
|
We don't actually need a dedicated component to do this but we'll make one anyway.
|
||||||
|
|
||||||
|
code-example(format="linenums" language="html" ).
|
||||||
|
@Component({
|
||||||
|
selector: 'loop-back',
|
||||||
|
template: `<input #box (keyup)="0"> <p>{{box.value}}</p>`
|
||||||
|
})
|
||||||
|
class LoopbackComponent {
|
||||||
|
}
|
||||||
|
|
||||||
code-example(language="html" format="linenums").
|
:markdown
|
||||||
<input #todotext>
|
We've declared a template local variable named `box` on the `<input>` element.
|
||||||
p.
|
The `box` variable is a reference to the `<input>` element itself which means we can
|
||||||
Lastly, specify the target of the click event binding as your controller's <code>addTodo()</code> method and pass
|
grab the input element's `value` and display it
|
||||||
it the value. Since you created a reference called <code>todotext</code>, you can get the value with
|
with interpolation between `<p>` tags. The display updates as we type. *Voila!*
|
||||||
<code>todotext.value.</code>
|
|
||||||
|
**This won't work at all unless we 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.
|
||||||
|
|
||||||
|
In this silly example we aren't really interested in the event at all.
|
||||||
|
But an Event Binding requires a template expression to evaluate when the event fires.
|
||||||
|
Many things qualify as expressions, none simpler than a one-character literal
|
||||||
|
like the number zero. That's all it takes to keep Angular happy. We said it would be clever!
|
||||||
|
|
||||||
|
That local template variable is intriguing. It's clearly easer to get to the textbox with that
|
||||||
|
variable than to go through the `$event` object. Maybe we can re-write our previous
|
||||||
|
example using the variable to acquire the user's' input. Let's give it a try.
|
||||||
|
code-example(format="linenums" language="html" ).
|
||||||
|
@Component({
|
||||||
|
selector: 'key-up2',
|
||||||
|
template: `
|
||||||
|
<h4>Give me some more keys!</h4>
|
||||||
|
<div><input #box (keyup)="onKey(box.value)"><div>
|
||||||
|
<div>{{values}}</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
class KeyUpComponentV2 {
|
||||||
|
values='';
|
||||||
|
onKey(value) {
|
||||||
|
this.values += value + ' | ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:markdown
|
||||||
|
That sure seems easier.
|
||||||
|
An especially nice aspect of this approach is that our component code gets clean data values from the view.
|
||||||
|
It no longer requires knowledge of the `$event` and its structure.
|
||||||
|
|
||||||
|
|
||||||
code-example(language="html" format="linenums").
|
|
||||||
<button (click)="addTodo(todotext.value)">Add Todo</button>
|
.l-main-section
|
||||||
|
:markdown
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
Let's put it all together in a micro-app
|
||||||
|
that can display a list of heroes and add new heroes to that list.
|
||||||
|
|
||||||
p And then create the <code>doneTyping()</code> method on TodoList and handle adding the todo text.
|
figure.image-display
|
||||||
|
img(src='/resources/images/devguide/user-input/little-tour.png' alt="Little Tour of Heroes")
|
||||||
code-example(language="javascript" format="linenums").
|
:markdown
|
||||||
doneTyping($event) {
|
Below is the entire "Little Tour of Heroes" micro-app in a single component.
|
||||||
if($event.which === 13) {
|
We'll call out the highlights after we bask briefly in its minimalist glory.
|
||||||
this.addTodo($event.target.value);
|
<!--
|
||||||
$event.target.value = null;
|
This example in http://plnkr.co/edit/JWeIqq
|
||||||
|
-->
|
||||||
|
code-example(format="linenums" language="html" ).
|
||||||
|
import {bootstrap, Component CORE_DIRECTIVES} from 'angular2/core'
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'little-tour',
|
||||||
|
template: `
|
||||||
|
<h4>Little Tour of Heroes</h4>
|
||||||
|
<input #new-hero (keyup.enter)="addHero(newHero)">
|
||||||
|
<button (click)=addHero(newHero)>Add</button>
|
||||||
|
<ul><li *ng-for="#hero of heroes">{{hero}}</li></ul>
|
||||||
|
<div>There are so many heroes!</div>
|
||||||
|
`,
|
||||||
|
directives: [CORE_DIRECTIVES]
|
||||||
|
})
|
||||||
|
class LittleTour {
|
||||||
|
heroes=['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
|
||||||
|
|
||||||
|
addHero(newHero) {
|
||||||
|
if (newHero.value) {
|
||||||
|
this.heroes.push(newHero.value);
|
||||||
|
newHero.value = null; // clear the newHero textbox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bootstrap(LittleTour);
|
||||||
|
|
||||||
|
:markdown
|
||||||
|
We've seen almost everything here before. A few things are new or bear repeating.
|
||||||
|
|
||||||
|
### **Beware of camelCase variable names**
|
||||||
|
We enter new hero names in the `<input>` element so we chose `newHero` to be the name of the local template variable.
|
||||||
|
|
||||||
|
Unfortunately, we can't use that name when we declare the variable with (#).
|
||||||
|
The browser forces all attribute and element names to lowercase, turning what would be `#newHero`
|
||||||
|
into `#newhero`. We don't want a `newhero` variable name in our template expressions.
|
||||||
|
The Angular workaround is to spell the declaration in "snake case". When Angular encounters "#new-hero",it translates
|
||||||
|
that to `newHero` for template expressions ... which is exactly what we want.
|
||||||
|
|
||||||
|
### **keyup.enter - a KeyEvent filter**
|
||||||
|
We'll add a hero name when the user clicks the "Add" button or hits the enter key. We ignore all other keys.
|
||||||
|
If we bind to `(keyup)` our event handling expression hears every key event. We'd have to
|
||||||
|
examine every `$event.keyCode` and respond only if the value 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.
|
||||||
|
Then either the `keyup.enter` or the button click event
|
||||||
|
can invoke the component's `addHero` method.
|
||||||
|
|
||||||
|
### **newHero refers to the `<input>` element**
|
||||||
|
We can access the `newHero` variable from any sibling or child of the `<input>` element.
|
||||||
|
When the user clicks the button, we don't need a fancy css selector to
|
||||||
|
track down the textbox and extract its value.
|
||||||
|
The button simply passes the `newHero` textbox reference to its own click handling method.
|
||||||
|
That's a tremendous simplification, as anyone who's wrangled jQuery can confirm.
|
||||||
|
|
||||||
|
Ready access to the `<input>` element also makes it easy for the `addHero` method
|
||||||
|
to clear the textbox after processing the new hero.
|
||||||
|
|
||||||
|
### **The *ng-for repeater**
|
||||||
|
The `ng-for` directive repeats the template as many times as there are heroes in the `heroes` list.
|
||||||
|
We must remember to list `NgFor` among the directives used by the component's template
|
||||||
|
by importing the `CORE_DIRECTIVES` constant and adding it to the
|
||||||
|
@Component decorator's `directives` array.
|
||||||
|
|
||||||
|
We learned about `NgFor` in the "[Displaying Data](./displaying-data.html)" chapter.
|
||||||
|
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
h2#section-final-code Final Code
|
:markdown
|
||||||
|
## Next Steps
|
||||||
code-tabs
|
|
||||||
code-pane(language="javascript" name="TypeScript" format="linenums").
|
We've mastered the basic primitives for responding to user input and gestures.
|
||||||
//TypeScript
|
As powerful as these primitives are, they are a bit clumsy for handling
|
||||||
import {Component, View, bootstrap, NgFor, NgIf} from 'angular2/angular2';
|
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.
|
||||||
@Component({
|
|
||||||
selector: 'todo-list'
|
Angular has a two-way binding called `NgModel` and we learn about it
|
||||||
template: `
|
in the `Forms` chapter.
|
||||||
<ul>
|
|
||||||
<li *ngfor="#todo of todos">
|
|
||||||
{{ todo }}
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<input #todotext (keyup)="doneTyping($event)">
|
|
||||||
<button (click)="addTodo(todotext.value)">Add Todo</button>
|
|
||||||
`,
|
|
||||||
directives: [NgFor, NIf]
|
|
||||||
})
|
|
||||||
class TodoList {
|
|
||||||
todos: Array<string>;
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"];
|
|
||||||
}
|
|
||||||
|
|
||||||
addTodo(todo: string) {
|
|
||||||
this.todos.push(todo);
|
|
||||||
}
|
|
||||||
|
|
||||||
doneTyping($event) {
|
|
||||||
if($event.which === 13) {
|
|
||||||
this.addTodo($event.target.value);
|
|
||||||
$event.target.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bootstrap(TodoList);
|
|
||||||
code-pane(language="javascript" name="ES5" format="linenums").
|
|
||||||
//ES5
|
|
||||||
function TodoList() {
|
|
||||||
this.todos = ["Eat Breakfast", "Walk Dog", "Breathe"];
|
|
||||||
|
|
||||||
this.addTodo = function(todo) {
|
|
||||||
this.todos.push(todo);
|
|
||||||
};
|
|
||||||
|
|
||||||
this.doneTyping = function($event) {
|
|
||||||
if($event.which === 13) {
|
|
||||||
this.addTodo($event.target.value);
|
|
||||||
$event.target.value = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
TodoList.annotations = [
|
|
||||||
new angular.ComponentAnnotation({
|
|
||||||
selector: "todo-list"
|
|
||||||
}),
|
|
||||||
new angular.ViewAnnotation({
|
|
||||||
template:
|
|
||||||
'<ul>' +
|
|
||||||
'<li *ng-for="#todo of todos">' +
|
|
||||||
'{{ todo }}' +
|
|
||||||
'</li>' +
|
|
||||||
'</ul>' +
|
|
||||||
'<input #textbox (keyup)="doneTyping($event)">' +
|
|
||||||
'<button (click)="addTodo(textbox.value)">Add Todo</button>',
|
|
||||||
directives: [angular.NgFor, angular.NgIf]
|
|
||||||
})
|
|
||||||
];
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", function() {
|
|
||||||
angular.bootstrap(TodoList);
|
|
||||||
});
|
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
Loading…
Reference in New Issue