diff --git a/public/docs/ts/latest/guide/_data.json b/public/docs/ts/latest/guide/_data.json index b8684eec1d..ed05038e70 100644 --- a/public/docs/ts/latest/guide/_data.json +++ b/public/docs/ts/latest/guide/_data.json @@ -16,7 +16,7 @@ "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": { diff --git a/public/docs/ts/latest/guide/template-syntax.jade b/public/docs/ts/latest/guide/template-syntax.jade index dcee33f513..076ab63872 100644 --- a/public/docs/ts/latest/guide/template-syntax.jade +++ b/public/docs/ts/latest/guide/template-syntax.jade @@ -30,7 +30,7 @@ include ../../../../_includes/_util-fns >[* and <template>](#star-template) - >[Local variables](#local-vars) + >[Local template variables](#local-vars) >[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 template expression usually references that 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. Another is the **`$event`** variable that contains information about an event raised on an element; diff --git a/public/docs/ts/latest/guide/user-input.jade b/public/docs/ts/latest/guide/user-input.jade index f6280732dd..c8e5cfb7c7 100644 --- a/public/docs/ts/latest/guide/user-input.jade +++ b/public/docs/ts/latest/guide/user-input.jade @@ -1,183 +1,244 @@ -.l-main-section - h2#section-responding-to-user-input Responding to user input with event syntax +:markdown + 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. - 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: (event). A controller function is then assigned to the event name: (event)="controllerFn()". - p. - For a particular control like an input you can have it call methods on your controller on keyup event like so: +:markdown + ## Binding to User Input Events - code-example(language="html"). - <input (keyup)="myControllerMethod()"> - - h3#local-variables Local variables - p. - As in previous examples, you can make element references available to other parts of the template as a local - variable using the #var syntax. With this and events, we can do the old "update text as you type" example: - - code-example(language="html"). - <input #myname (keyup)> - <p>{{myname.value}}</p> - - p.text-body(ng-non-bindable). - The #myname creates a local variable in the template that we'll refer to below in the - <p> element. The (keyup) tells Angular to trigger updates when it gets a keyup - event. And the {{myname.value}} binds the text node of the <p> element to the - input's value property. - p Let's do something a little more complex where users enter items and add them to a list like this: - - figure.image-display - img(src='/resources/images/examples/user-input-example1.png' alt="Example of Todo App") - - -.l-ain-section - h2#section-create-an-array-property Create an array property - p. - With the default bootstrapping in place, create a controller class that will manage interactions with the - list. Inside the controller, add an array with an initial list of items. Then add a method that pushes new items - on the array when called. - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //TypeScript - class TodoList { - 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 TodoList. We've omitted it here for brevity. + We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events) + with an [Angular Event Binding](./template-syntax.html#event-binding). + + The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses. + A click Event Binding makes for a quick illustration. + +code-example(language="html" ). + <button (click)="onClickMe()">Click me</button> +:markdown + 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 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 + of JavaScript with a few added tricks. + + When writing a binding we must be aware of a template expression's **execution context**. + The identifers appearing within an expression 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: + + ``` + @Component({ + selector: 'click-me', + template: '' + }) + class ClickMeComponent { + onClickMe(){ + alert('You are my hero!') + } + } + ``` + The `onClickMe` in the template refers to the `onClickMe` method of the component. + When the user clicks the button, Angular calls the component's `onClickMe` method. .l-main-section - h2#section-display-the-list-of-todos Display the list of todos - p. - Using the *ng-for iterator, create an <li> for each item in the todos array and set - its text to the value. +:markdown + ## 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. + + 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"). - <ul> - <li *ng-for="#todo of todos"> - {{ todo }} - </li> - </ul> +:markdown .l-main-section - h2#section-add-todos-to-the-list Add todos to the list via button click - p. - 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 - variable reference in your template with #varname. Call it #todotext here. +:markdown + ## Get user input from a local template variable + There's another way to get the user data without the `$event` variable. + + 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"). - <input #todotext> - p. - Lastly, specify the target of the click event binding as your controller's addTodo() method and pass - it the value. Since you created a reference called todotext, you can get the value with - todotext.value. +:markdown + We've declared a template local variable named `box` on the `` element. + The `box` variable is a reference to the `` element itself which means we can + grab the input element's `value` and display it + with interpolation between `

` tags. The display updates as we type. *Voila!* + + **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 doneTyping() method on TodoList and handle adding the todo text. - - code-example(language="javascript" format="linenums"). - doneTyping($event) { - if($event.which === 13) { - this.addTodo($event.target.value); - $event.target.value = null; +figure.image-display + img(src='/resources/images/devguide/user-input/little-tour.png' alt="Little Tour of Heroes") +:markdown + Below is the entire "Little Tour of Heroes" micro-app in a single component. + We'll call out the highlights after we bask briefly in its minimalist glory. + +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 `` 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 `` element** + We can access the `newHero` variable from any sibling or child of the `` 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 `` 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 - h2#section-final-code Final Code - - code-tabs - code-pane(language="javascript" name="TypeScript" format="linenums"). - //TypeScript - import {Component, View, bootstrap, NgFor, NgIf} from 'angular2/angular2'; - - @Component({ - selector: 'todo-list' - template: ` - <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); - }); - +:markdown + ## Next Steps + + 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. + + Angular has a two-way binding called `NgModel` and we learn about it + in the `Forms` chapter. + + + \ No newline at end of file diff --git a/public/resources/images/devguide/user-input/little-tour.png b/public/resources/images/devguide/user-input/little-tour.png new file mode 100644 index 0000000000..5758505843 Binary files /dev/null and b/public/resources/images/devguide/user-input/little-tour.png differ