parent
1fc7df82b5
commit
693c5e60e6
|
@ -1,26 +1,27 @@
|
||||||
include ../../../../_includes/_util-fns
|
include ../../../../_includes/_util-fns
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
When the user clicks a link, pushes a button, or types on the keyboard
|
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.
|
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.
|
In this chapter we learn to bind to those events using the Angular
|
||||||
|
event binding syntax.
|
||||||
|
|
||||||
[Live Example](/resources/live-examples/user-input/ts/plnkr.html).
|
[Run the live example](/resources/live-examples/user-input/ts/plnkr.html)
|
||||||
|
|
||||||
:marked
|
:marked
|
||||||
## Binding to User Input Events
|
## Binding to user input events
|
||||||
|
|
||||||
We can listen to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events)
|
We can use [Angular event bindings](./template-syntax.html#event-binding)
|
||||||
with an [Angular Event Binding](./template-syntax.html#event-binding).
|
to respond to [any DOM event](https://developer.mozilla.org/en-US/docs/Web/Events).
|
||||||
|
|
||||||
The syntax is simple. We assign a template expression to the DOM event name, surrounded in parentheses.
|
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.
|
As an example, here's an event binding that implements a click handler:
|
||||||
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-button')(format=".", language="html")
|
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-button')(format=".", language="html")
|
||||||
|
|
||||||
<a id="click"></a>
|
<a id="click"></a>
|
||||||
:marked
|
:marked
|
||||||
The `(click)` to the left of the equal sign identifies the button's click event as the **target of the binding**.
|
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
|
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
|
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.
|
of JavaScript with a few added tricks.
|
||||||
|
|
||||||
|
@ -29,37 +30,37 @@ include ../../../../_includes/_util-fns
|
||||||
That object is usually the Angular component that controls the template ... which it definitely is
|
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:
|
in this case because that snippet of HTML belongs to the following component:
|
||||||
|
|
||||||
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-component', 'app/click-me.component.ts')
|
+makeExample('user-input/ts/app/click-me.component.ts', 'click-me-component', 'app/click-me.component.ts')(format=".")
|
||||||
:marked
|
:marked
|
||||||
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.
|
When the user clicks the button, Angular calls the component's `onClickMe` method.
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Get user input from the $event object
|
## 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
|
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.
|
what the user types back onto the screen.
|
||||||
|
|
||||||
This time we'll (1) listen to an event and (2) grab the user's input.
|
This time we'll (1) listen to an event and (2) grab the user's input.
|
||||||
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-template', 'app/keyup.components.ts (template v.1)')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-template', 'app/keyup.components.ts (template v.1)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Angular makes an event object available in the **`$event`** variable
|
Angular makes an event object available in the **`$event`** variable,
|
||||||
which we pass to the component's `onKey()` method.
|
which we pass to the component's `onKey()` method.
|
||||||
The user data we want is in that variable somewhere.
|
The user data we want is in that variable somewhere.
|
||||||
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class-no-type', 'app/keyup.components.ts (class v.1)')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class-no-type', 'app/keyup.components.ts (class v.1)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
The shape of the `$event` object is determined by whatever raises the event.
|
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 `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
|
The `$event.target` gives us an
|
||||||
[`HTMLInputElement`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement) which
|
[`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.
|
has a `value` property that contains our user input data.
|
||||||
|
|
||||||
We had this in mind when we passed `$event` to our `onKey()` component method. This is where we extract the user's input and
|
The `onKey()` component method is where we extract the user's input
|
||||||
concatenate it to the previous user data that we're accumulating in the component's `values` property.
|
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)
|
We then use [interpolation](./template-syntax.html#interpolation)
|
||||||
to display the accumulating `values` property back on screen.
|
to display the accumulating `values` property back on screen.
|
||||||
|
|
||||||
Enter the letters "abc", backspace to remove them, and we should see:
|
Enter the letters "abc", and then backspace to remove them.
|
||||||
|
Here's what the UI displays:
|
||||||
code-example().
|
code-example().
|
||||||
a | ab | abc | ab | a | |
|
a | ab | abc | ab | a | |
|
||||||
figure.image-display
|
figure.image-display
|
||||||
|
@ -68,10 +69,10 @@ figure.image-display
|
||||||
<a id="keyup1"></a>
|
<a id="keyup1"></a>
|
||||||
.l-sub-section
|
.l-sub-section
|
||||||
:marked
|
:marked
|
||||||
We cast the `$event` as an `any` type which means we've abandoned strong typing
|
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.
|
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.
|
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 )')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-1-class', 'app/keyup.components.ts (class v.1 - strongly typed )')(format=".")
|
||||||
:marked
|
:marked
|
||||||
<br>Strong typing reveals a serious problem with passing a DOM event into the method:
|
<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.
|
too much awareness of template details, too little separation of concerns.
|
||||||
|
@ -88,17 +89,19 @@ figure.image-display
|
||||||
These variables grant us direct access to an element.
|
These variables grant us direct access to an element.
|
||||||
We declare a local template variable by preceding an identifier with a hash/pound character (#).
|
We declare a local template variable by preceding an identifier with a hash/pound character (#).
|
||||||
|
|
||||||
Let's demonstrate with a clever keystroke loopback in an ultra-simple template.
|
Here's an example of using a local template variable
|
||||||
+makeExample('user-input/ts/app/loop-back.component.ts', 'loop-back-component', 'app/loop-back.component.ts')
|
to implement a clever keystroke loopback in an ultra-simple template.
|
||||||
|
+makeExample('user-input/ts/app/loop-back.component.ts', 'loop-back-component', 'app/loop-back.component.ts')(format=".")
|
||||||
:marked
|
:marked
|
||||||
We've declared a template local variable named `box` on the `<input>` element.
|
We've declared a template local variable named `box` on the `<input>` element.
|
||||||
The `box` variable is a reference to the `<input>` element itself which means we can
|
The `box` variable is a reference to the `<input>` element itself, which means we can
|
||||||
grab the input element's `value` and display it
|
grab the input element's `value` and display it
|
||||||
with interpolation between `<p>` tags.
|
with interpolation between `<p>` tags.
|
||||||
|
|
||||||
The template is completely self contained. It doesn't bind to the component which does nothing.
|
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 in the input box, and watch the display update with each keystroke. *Voila!*
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back")
|
img(src='/resources/images/devguide/user-input/keyup-loop-back-anim.gif' alt="loop back")
|
||||||
|
@ -114,9 +117,9 @@ figure.image-display
|
||||||
That is all it takes to keep Angular happy. We said it would be clever!
|
That is all it takes to keep Angular happy. We said it would be clever!
|
||||||
:marked
|
:marked
|
||||||
That local template variable is intriguing. It's clearly easier to get to the textbox with that
|
That local template 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 re-write our previous
|
variable than to go through the `$event` object. Maybe we can rewrite our previous
|
||||||
"key-up" example using the variable to acquire the user's input. Let's give it a try.
|
keyup example so that it uses the variable to get the user's input. Let's give it a try.
|
||||||
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-2' ,'app/keyup.components.ts (v2)')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-2' ,'app/keyup.components.ts (v2)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
That sure seems easier.
|
That sure seems easier.
|
||||||
An especially nice aspect of this approach is that our component code gets clean data values from the view.
|
An especially nice aspect of this approach is that our component code gets clean data values from the view.
|
||||||
|
@ -127,17 +130,17 @@ figure.image-display
|
||||||
:marked
|
:marked
|
||||||
## Key event filtering (with `key.enter`)
|
## Key event filtering (with `key.enter`)
|
||||||
Perhaps we don't care about every keystroke.
|
Perhaps we don't care about every keystroke.
|
||||||
We're only interested in the input box value when the user hits the "Enter" key. We'd like to ignore all other keys.
|
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 expression hears *every key stroke*.
|
When we bind to the `(keyup)` event, our event handling expression hears *every keystroke*.
|
||||||
We could filter the keys first, examining every `$event.keyCode`, and update the `values` property only if the key is "Enter".
|
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.
|
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.
|
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 ...
|
Only then do we update the component's `values` property. (In this example,
|
||||||
inside the event expression rather than in the component ...
|
the update happens inside the event expression. A better practice
|
||||||
because we *can* ... even if it is a dubious practice.
|
would be to put the update code in the component.)
|
||||||
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-3' ,'app/keyup.components.ts (v3)')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-3' ,'app/keyup.components.ts (v3)')(format=".")
|
||||||
:marked
|
:marked
|
||||||
Here's how it works.
|
Here's how it works.
|
||||||
figure.image-display
|
figure.image-display
|
||||||
|
@ -148,12 +151,12 @@ figure.image-display
|
||||||
## On blur
|
## On blur
|
||||||
|
|
||||||
Our previous example won't transfer the current state of the input box if the user mouses away and clicks
|
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 only update the component's `values` property when the user presses "Enter"
|
elsewhere on the page. We update the component's `values` property only when the user presses Enter
|
||||||
inside the input box.
|
while the focus is inside the input box.
|
||||||
|
|
||||||
Let's fix that by listening to the input box's blur event as well.
|
Let's fix that by listening to the input box's blur event as well.
|
||||||
|
|
||||||
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-4' ,'app/keyup.components.ts (v4)')
|
+makeExample('user-input/ts/app/keyup.components.ts', 'key-up-component-4' ,'app/keyup.components.ts (v4)')(format=".")
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
|
@ -162,9 +165,9 @@ figure.image-display
|
||||||
We've acquired a small arsenal of event binding techniques in this chapter.
|
We've acquired a small arsenal of event binding techniques in this chapter.
|
||||||
|
|
||||||
Let's put it all together in a micro-app
|
Let's put it all together in a micro-app
|
||||||
that can display a list of heroes and add new heroes to that list
|
that can display a list of heroes and add new heroes to that list.
|
||||||
by typing in the input box and hitting "Enter", clicking "Add", or clicking
|
The user can add a hero by first typing in the input box and then
|
||||||
elsewhere on the page.
|
pressing Enter, clicking the Add button, or clicking elsewhere on the page.
|
||||||
|
|
||||||
figure.image-display
|
figure.image-display
|
||||||
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
|
img(src='/resources/images/devguide/user-input/little-tour-anim.gif' alt="Little Tour of Heroes")
|
||||||
|
@ -172,44 +175,46 @@ figure.image-display
|
||||||
Below is the "Little Tour of Heroes" component.
|
Below is the "Little Tour of Heroes" component.
|
||||||
We'll call out the highlights after we bask briefly in its minimalist glory.
|
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')
|
+makeExample('user-input/ts/app/little-tour.component.ts', 'little-tour', 'app/little-tour.component.ts')(format=".")
|
||||||
:marked
|
:marked
|
||||||
We've seen almost everything here before. A few things are new or bear repeating.
|
We've seen almost everything here before. A few things are new or bear repeating.
|
||||||
|
|
||||||
### *newHero* template variable
|
### Use template variables to refer to elements
|
||||||
|
|
||||||
The *newHero* template variable refers to the `<input>` element.
|
The `newHero` template variable refers to the `<input>` element.
|
||||||
|
We can use `newHero` from any sibling or child of the `<input>` element.
|
||||||
|
|
||||||
We can access `newHero` from any sibling or child of the `<input>` element.
|
Getting the element from a template variable makes the button click handler
|
||||||
When the user clicks the button, we don't need a fancy CSS selector to
|
simpler. Without the variable, we'd have to use a fancy CSS selector
|
||||||
track down the textbox and extract its value.
|
to find the input element.
|
||||||
|
|
||||||
### Extract the input box *value*
|
### Pass values, not elements
|
||||||
We could have passed the `newHero` into the component's `addHero()` method.
|
|
||||||
|
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,
|
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 [*KeyupComponent*](#keyup1).
|
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()`.
|
Instead, we grab the input box *value* and pass *that* to `addHero`.
|
||||||
The component knows nothing about HTML or DOM which is the way we like it.
|
The component knows nothing about HTML or the DOM, which is the way we like it.
|
||||||
|
|
||||||
### Don't let template expressions be complex
|
### Keep template expressions simple
|
||||||
We bound `(blur)` to *two* JavaScript statements.
|
We bound `(blur)` to *two* JavaScript statements.
|
||||||
|
|
||||||
We like the first one that calls `addHero`.
|
We like the first one, which calls `addHero`.
|
||||||
We do not like the second one that assigns an empty string to the input box value.
|
We do not like the second one, which assigns an empty string to the input box value.
|
||||||
|
|
||||||
We did it for a good reason. We have to clear the input box after adding the new hero to the list.
|
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
|
The component has no way to do that itself because it has no access to the
|
||||||
input box (our design choice).
|
input box (our design choice).
|
||||||
|
|
||||||
Although it *works*, we are rightly wary of JavaScript in HTML.
|
Although the example *works*, we are rightly wary of JavaScript in HTML.
|
||||||
Template expressions are powerful. We're supposed to use them responsibly.
|
Template expressions are powerful. We're supposed to use them responsibly.
|
||||||
Complex JavaScript in HTML is irresponsible.
|
Complex JavaScript in HTML is irresponsible.
|
||||||
|
|
||||||
Should we reconsider our reluctance to pass the input box into the component?
|
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.
|
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
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Source code
|
## Source code
|
||||||
|
@ -228,12 +233,12 @@ figure.image-display
|
||||||
|
|
||||||
.l-main-section
|
.l-main-section
|
||||||
:marked
|
:marked
|
||||||
## Next Steps
|
## Summary
|
||||||
|
|
||||||
We've mastered the basic primitives for responding to user input and gestures.
|
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
|
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
|
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.
|
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
|
Angular has a two-way binding called `NgModel`, which we'll learn about
|
||||||
in the `Forms` chapter.
|
in the `Forms` chapter.
|
||||||
|
|
Loading…
Reference in New Issue