用户输入

User Input

当用户点击链接、按下按钮或者输入文字时,这些用户动作都会产生 DOM 事件。 本章解释如何使用 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在线例子 / 下载范例.

绑定到用户输入事件

Binding to user input events

你可以使用 Angular 事件绑定机制来响应任何 DOM 事件。 许多 DOM 事件是由用户输入触发的。绑定这些事件可以获取用户输入。

You can use Angular event bindings to respond to any DOM event. Many DOM events are triggered by user input. Binding to these events provides a way to get input from the user.

要绑定 DOM 事件,只要把 DOM 事件的名字包裹在圆括号中,然后用放在引号中的模板语句对它赋值就可以了。

To bind to a DOM event, surround the DOM event name in parentheses and assign a quoted template statement to it.

下例展示了一个事件绑定,它实现了一个点击事件处理器:

The following example shows an event binding that implements a click handler:

<button (click)="onClickMe()">Click me!</button>
src/app/click-me.component.ts
      
      <button (click)="onClickMe()">Click me!</button>
    

等号左边的 (click) 表示把按钮的点击事件作为绑定目标。 等号右边引号中的文本是模板语句,通过调用组件的 onClickMe 方法来响应这个点击事件。

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.

写绑定时,需要知道模板语句的执行上下文。 出现在模板语句中的每个标识符都属于特定的上下文对象。 这个对象通常都是控制此模板的 Angular 组件。 上例中只显示了一行 HTML,那段 HTML 片段属于下面这个组件:

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:

@Component({ selector: 'app-click-me', template: ` <button (click)="onClickMe()">Click me!</button> {{clickMessage}}` }) export class ClickMeComponent { clickMessage = ''; onClickMe() { this.clickMessage = 'You are my hero!'; } }
src/app/click-me.component.ts
      
      @Component({
  selector: 'app-click-me',
  template: `
    <button (click)="onClickMe()">Click me!</button>
    {{clickMessage}}`
})
export class ClickMeComponent {
  clickMessage = '';

  onClickMe() {
    this.clickMessage = 'You are my hero!';
  }
}
    

当用户点击按钮时,Angular 调用 ClickMeComponentonClickMe 方法。

When the user clicks the button, Angular calls the onClickMe method from ClickMeComponent.

通过 $event 对象取得用户输入

Get user input from the $event object

DOM 事件可以携带可能对组件有用的信息。 本节将展示如何绑定输入框的 keyup 事件,在每个敲击键盘时获取用户输入。

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.

下面的代码监听 keyup 事件,并将整个事件载荷 ($event) 传递给组件的事件处理器。

The following code listens to the keyup event and passes the entire event payload ($event) to the component event handler.

template: ` <input (keyup)="onKey($event)"> <p>{{values}}</p> `
src/app/keyup.components.ts (template v.1)
      
      template: `
  <input (keyup)="onKey($event)">
  <p>{{values}}</p>
`
    

当用户按下并释放一个按键时,触发 keyup 事件,Angular 在 $event 变量提供一个相应的 DOM 事件对象,上面的代码将它作为参数传递给 onKey() 方法。

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.

export class KeyUpComponent_v1 { values = ''; onKey(event: any) { // without type info this.values += event.target.value + ' | '; } }
src/app/keyup.components.ts (class v.1)
      
      export class KeyUpComponent_v1 {
  values = '';

  onKey(event: any) { // without type info
    this.values += event.target.value + ' | ';
  }
}
    

$event 对象的属性取决于 DOM 事件的类型。例如,鼠标事件与输入框编辑事件包含了不同的信息。

The properties of an $event object vary depending on the type of DOM event. For example, a mouse event includes different information than an input box editing event.

所有标准 DOM 事件对象都有一个 target 属性, 引用触发该事件的元素。 在本例中,target<input> 元素event.target.value 返回该元素的当前内容。

All standard DOM event objects have a target property, a reference to the element that raised the event. In this case, target refers to the <input> element and event.target.value returns the current contents of that element.

在组件的 onKey() 方法中,把输入框的值和分隔符 (|) 追加组件的 values 属性。 使用插值表达式来把存放累加结果的 values 属性回显到屏幕上。

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 displays the accumulating input box changes from the values property.

假设用户输入字母“abc”,然后用退格键一个一个删除它们。 用户界面将显示:

Suppose the user enters the letters "abc", and then backspaces to remove them one by one. Here's what the UI displays:

a | ab | abc | ab | a | |
      
      a | ab | abc | ab | a | |
    
key up 1

或者,你可以用 event.key 替代 event.target.value,积累各个按键本身,这样同样的用户输入可以产生:

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:

a | b | c | backspace | backspace | backspace |
      
      a | b | c | backspace | backspace | backspace |
    

$event的类型

Type the $event

上例将 $event 转换为 any 类型。 这样简化了代码,但是有成本。 没有任何类型信息能够揭示事件对象的属性,防止简单的错误。

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:

export class KeyUpComponent_v1 { values = ''; onKey(event: KeyboardEvent) { // with type info this.values += (<HTMLInputElement>event.target).value + ' | '; } }
src/app/keyup.components.ts (class v.1 - typed )
      
      export class KeyUpComponent_v1 {
  values = '';


  onKey(event: KeyboardEvent) { // with type info
    this.values += (<HTMLInputElement>event.target).value + ' | ';
  }
}
    

$event 的类型现在是 KeyboardEvent。 不是所有的元素都有 value 属性,所以它将 target 转换为输入元素。 OnKey 方法更加清晰地表达了它期望从模板得到什么,以及它是如何解析事件的。

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.

传入 $event 是靠不住的做法

Passing $event is a dubious practice

类型化事件对象揭露了重要的一点,即反对把整个 DOM 事件传到方法中,因为这样组件会知道太多模板的信息。 只有当它知道更多它本不应了解的 HTML 实现细节时,它才能提取信息。 这就违反了模板(用户看到的)和组件(应用如何处理用户数据)之间的分离关注原则。

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.

从一个模板引用变量中获得用户输入

Get user input from a template reference variable

还有另一种获取用户数据的方式:使用 Angular 的模板引用变量。 这些变量提供了从模块中直接访问元素的能力。 在标识符前加上井号 (#) 就能声明一个模板引用变量。

There's another way to get the user data: use Angular template reference variables. 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 (#).

下面的例子使用了局部模板变量,在一个超简单的模板中实现按键反馈功能。

The following example uses a template reference variable to implement a keystroke loopback in a simple template.

@Component({ selector: 'app-loop-back', template: ` <input #box (keyup)="0"> <p>{{box.value}}</p> ` }) export class LoopbackComponent { }
src/app/loop-back.component.ts
      
      @Component({
  selector: 'app-loop-back',
  template: `
    <input #box (keyup)="0">
    <p>{{box.value}}</p>
  `
})
export class LoopbackComponent { }
    

这个模板引用变量名叫 box,在 <input> 元素声明,它引用 <input> 元素本身。 代码使用 box 获得输入元素的 value 值,并通过插值表达式把它显示在 <p> 标签中。

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 something in the input box, and watch the display update with each keystroke.

loop back

除非你绑定一个事件,否则这将完全无法工作。

This won't work at all unless you bind to an event.

只有在应用做了些异步事件(如击键),Angular 才更新绑定(并最终影响到屏幕)。 本例代码将 keyup 事件绑定到了数字 0,这可能是最短的模板语句了。 虽然这个语句不做什么,但它满足 Angular 的要求,所以 Angular 将更新屏幕。

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.

从模板变量获得输入框比通过 $event 对象更加简单。 下面的代码重写了之前 keyup 示例,它使用变量来获得用户输入。

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.

@Component({ selector: 'app-key-up2', template: ` <input #box (keyup)="onKey(box.value)"> <p>{{values}}</p> ` }) export class KeyUpComponent_v2 { values = ''; onKey(value: string) { this.values += value + ' | '; } }
src/app/keyup.components.ts (v2)
      
      @Component({
  selector: 'app-key-up2',
  template: `
    <input #box (keyup)="onKey(box.value)">
    <p>{{values}}</p>
  `
})
export class KeyUpComponent_v2 {
  values = '';
  onKey(value: string) {
    this.values += value + ' | ';
  }
}
    

这个方法最漂亮的一点是:组件代码从视图中获得了干净的数据值。再也不用了解 $event 变量及其结构了。

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.

按键事件过滤(通过 key.enter

Key event filtering (with key.enter)

(keyup) 事件处理器监听每一次按键。 有时只在意回车键,因为它标志着用户结束输入。 解决这个问题的一种方法是检查每个 $event.keyCode,只有键值是回车键时才采取行动。

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.

更简单的方法是:绑定到 Angular 的 keyup.enter 模拟事件。 然后,只有当用户敲回车键时,Angular 才会调用事件处理器。

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.

@Component({ selector: 'app-key-up3', template: ` <input #box (keyup.enter)="onEnter(box.value)"> <p>{{value}}</p> ` }) export class KeyUpComponent_v3 { value = ''; onEnter(value: string) { this.value = value; } }
src/app/keyup.components.ts (v3)
      
      @Component({
  selector: 'app-key-up3',
  template: `
    <input #box (keyup.enter)="onEnter(box.value)">
    <p>{{value}}</p>
  `
})
export class KeyUpComponent_v3 {
  value = '';
  onEnter(value: string) { this.value = value; }
}
    

下面展示了它是如何工作的。

Here's how it works.

key up 3

失去焦点事件 (blur)

On blur

前上例中,如果用户没有先按回车键,而是移开了鼠标,点击了页面中其它地方,输入框的当前值就会丢失。 只有当用户按下了回车键候,组件的 values 属性才能更新。

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.

下面通过同时监听输入框的回车键和失去焦点事件来修正这个问题。

To fix this issue, listen to both the Enter key and the blur event.

@Component({ selector: 'app-key-up4', template: ` <input #box (keyup.enter)="update(box.value)" (blur)="update(box.value)"> <p>{{value}}</p> ` }) export class KeyUpComponent_v4 { value = ''; update(value: string) { this.value = value; } }
src/app/keyup.components.ts (v4)
      
      @Component({
  selector: 'app-key-up4',
  template: `
    <input #box
      (keyup.enter)="update(box.value)"
      (blur)="update(box.value)">

    <p>{{value}}</p>
  `
})
export class KeyUpComponent_v4 {
  value = '';
  update(value: string) { this.value = value; }
}
    

把它们放在一起

Put it all together

上一章介绍了如何显示数据。 本章展示了事件绑定技术。

The previous page showed how to display data. This page demonstrated event binding techniques.

现在,在一个微型应用中一起使用它们,应用能显示一个英雄列表,并把新的英雄加到列表中。 用户可以通过输入英雄名和点击“添加”按钮来添加英雄。

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.

Little Tour of Heroes

下面就是“简版英雄指南”组件。

Below is the "Little Tour of Heroes" component.

@Component({ selector: 'app-little-tour', template: ` <input #newHero (keyup.enter)="addHero(newHero.value)" (blur)="addHero(newHero.value); newHero.value='' "> <button (click)="addHero(newHero.value)">Add</button> <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul> ` }) export class LittleTourComponent { heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; addHero(newHero: string) { if (newHero) { this.heroes.push(newHero); } } }
src/app/little-tour.component.ts
      
      @Component({
  selector: 'app-little-tour',
  template: `
    <input #newHero
      (keyup.enter)="addHero(newHero.value)"
      (blur)="addHero(newHero.value); newHero.value='' ">

    <button (click)="addHero(newHero.value)">Add</button>

    <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
  `
})
export class LittleTourComponent {
  heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  addHero(newHero: string) {
    if (newHero) {
      this.heroes.push(newHero);
    }
  }
}
    

小结

Observations

  • 使用模板变量来引用元素newHero 模板变量引用了 <input> 元素。 你可以在 <input> 的任何兄弟或子级元素中引用 newHero

    Use template variables to refer to elements — The newHero template variable refers to the <input> element. You can reference newHero from any sibling or child of the <input> element.

  • 传递数值,而非元素 — 获取输入框的值并将传递给组件的 addHero,而不要传递 newHero

    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.

  • 保持模板语句简单(blur) 事件被绑定到两个 JavaScript 语句。 第一句调用 addHero。第二句 newHero.value='' 在添加新英雄到列表中后清除输入框。

    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.

源代码

Source code

下面是本章讨论过的所有源码。

Following is all the code discussed in this page.

import { Component } from '@angular/core'; @Component({ selector: 'app-click-me', template: ` <button (click)="onClickMe()">Click me!</button> {{clickMessage}}` }) export class ClickMeComponent { clickMessage = ''; onClickMe() { this.clickMessage = 'You are my hero!'; } }import { Component } from '@angular/core'; @Component({ selector: 'app-key-up1', template: ` <input (keyup)="onKey($event)"> <p>{{values}}</p> ` }) export class KeyUpComponent_v1 { values = ''; /* onKey(event: any) { // without type info this.values += event.target.value + ' | '; } */ onKey(event: KeyboardEvent) { // with type info this.values += (<HTMLInputElement>event.target).value + ' | '; } } ////////////////////////////////////////// @Component({ selector: 'app-key-up2', template: ` <input #box (keyup)="onKey(box.value)"> <p>{{values}}</p> ` }) export class KeyUpComponent_v2 { values = ''; onKey(value: string) { this.values += value + ' | '; } } ////////////////////////////////////////// @Component({ selector: 'app-key-up3', template: ` <input #box (keyup.enter)="onEnter(box.value)"> <p>{{value}}</p> ` }) export class KeyUpComponent_v3 { value = ''; onEnter(value: string) { this.value = value; } } ////////////////////////////////////////// @Component({ selector: 'app-key-up4', template: ` <input #box (keyup.enter)="update(box.value)" (blur)="update(box.value)"> <p>{{value}}</p> ` }) export class KeyUpComponent_v4 { value = ''; update(value: string) { this.value = value; } }import { Component } from '@angular/core'; @Component({ selector: 'app-loop-back', template: ` <input #box (keyup)="0"> <p>{{box.value}}</p> ` }) export class LoopbackComponent { }import { Component } from '@angular/core'; @Component({ selector: 'app-little-tour', template: ` <input #newHero (keyup.enter)="addHero(newHero.value)" (blur)="addHero(newHero.value); newHero.value='' "> <button (click)="addHero(newHero.value)">Add</button> <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul> ` }) export class LittleTourComponent { heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado']; addHero(newHero: string) { if (newHero) { this.heroes.push(newHero); } } }
      
      
  1. import { Component } from '@angular/core';
  2.  
  3. @Component({
  4. selector: 'app-click-me',
  5. template: `
  6. <button (click)="onClickMe()">Click me!</button>
  7. {{clickMessage}}`
  8. })
  9. export class ClickMeComponent {
  10. clickMessage = '';
  11.  
  12. onClickMe() {
  13. this.clickMessage = 'You are my hero!';
  14. }
  15. }

小结

Summary

你已经掌握了响应用户输入和操作的基础技术。

You have mastered the basic primitives for responding to user input and gestures.

这些技术对小规模演示很实用,但是在处理大量用户输入时,很容易变得累赘和笨拙。 要在数据录入字段和模型属性之间传递数据,双向数据绑定是更加优雅和简洁的方式。 下一章 表单 解释了如何用 NgModel 来进行双向绑定。

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.