diff --git a/public/docs/_examples/user-input/ts/app/keyup.components.ts b/public/docs/_examples/user-input/ts/app/keyup.components.ts index 79736c8a4c..aaf62bebaf 100644 --- a/public/docs/_examples/user-input/ts/app/keyup.components.ts +++ b/public/docs/_examples/user-input/ts/app/keyup.components.ts @@ -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 += (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: ` - -

{{values}}

+ +

{{value}}

` }) 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: ` + (keyup.enter)="update(box.value)" + (blur)="update(box.value)"> -

{{values}}

+

{{value}}

` }) export class KeyUpComponent_v4 { - values = ''; + value = ''; + update(value: string) { this.value = value; } } // #enddocregion key-up-component-4 diff --git a/public/docs/ts/latest/guide/user-input.jade b/public/docs/ts/latest/guide/user-input.jade index 85dc8e5d8f..46cf5474ec 100644 --- a/public/docs/ts/latest/guide/user-input.jade +++ b/public/docs/ts/latest/guide/user-input.jade @@ -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 . @@ -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") :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 [`` 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") - .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 -
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. - - 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 (#). + 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 (#). - 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 `` element. - The `box` variable is a reference to the `` element itself, which means we can - grab the input element's `value` and display it + The template reference variable named `box`, declared on the `` element, + refers to the `` element itself. + The code uses the `box` variable to get the input element's `value` and display it with interpolation between `

` 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**. - - 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! -: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. + **This won't work at all unless you bind to an event**. + + 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 + 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. .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** — The `newHero` template variable refers to the `` element. - We can use `newHero` from any sibling or child of the `` 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 - - We could have passed the `newHero` into the component's `addHero` method. - - But that would require `addHero` to pick its way through the `` 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). + You can reference `newHero` from any sibling or child of the `` element. - 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. + * **Pass values, not elements** — + Instead of passing the `newHero` into the component's `addHero` method, + get the input box value and pass *that* to `addHero`. - Should we reconsider our reluctance to pass the input box into the component? + * **Keep template statements simple** — + 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. - 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`.