填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

使用者輸入

User input

Marked for archiving

To ensure that you have the best experience possible, this topic is marked for archiving until we determine that it clearly conveys the most accurate information possible.

In the meantime, this topic might be helpful: Event binding.

If you think this content should not be archived, please file a GitHub issue.

當用戶點選連結、按下按鈕或者輸入文字時,這些使用者動作都會產生 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:

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:

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.

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.

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 | |
    

或者,你可以用 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 |
    

$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:

src/app/keyup.components.ts (class v.1 - typed )
      
      export class KeyUpComponent_v1 {
  values = '';


  onKey(event: KeyboardEvent) { // with type info
    this.values += (event.target as HTMLInputElement).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.

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.

除非你繫結一個事件,否則這將完全無法工作。

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.

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) 事件處理器監聽每一次按鍵。 有時只在意Enter鍵,因為它標誌著使用者結束輸入。 解決這個問題的一種方法是檢查每個 $event.keyCode,只有鍵值是Enter鍵時才採取行動。

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 模擬事件。 然後,只有當用戶敲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.

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.

失去焦點事件 (blur)

On blur

前上例中,如果使用者沒有先按回車鍵,而是移開了滑鼠,點選了頁面中其它地方,輸入框的當前值就會丟失。 只有當用戶按下了Enter鍵候,元件的 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.

下面透過同時監聽輸入框的Enter鍵和失去焦點事件來修正這個問題。

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

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

本章展示了一些事件繫結技術。

This page demonstrated several 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.

下面就是“簡版英雄之旅”元件。

Below is the "Little Tour of Heroes" component.

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!';
  }
}
    

Angular 還支援被動事件監聽器。例如,你可以使用以下步驟使滾動事件變為被動監聽。

Angular also supports passive event listeners. For example, you can use the following steps to make the scroll event passive.

  1. src 目錄下建立一個 zone-flags.ts 檔案。

    Create a file zone-flags.ts under src directory.

  2. 往這個檔案中新增如下語句。

    Add the following line into this file.

      
      (window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];
    
  1. src/polyfills.ts 檔案中,匯入 zone.js 之前,先匯入新建立的 zone-flags 檔案。

    In the src/polyfills.ts file, before importing zone.js, import the newly created zone-flags.

      
      import './zone-flags';
import 'zone.js';  // Included with Angular CLI.
    

經過這些步驟,你新增 scroll 事件的監聽器時,它就是被動(passive)的。

After those steps, if you add event listeners for the scroll event, the listeners will be passive.

小結

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 Formspage explains how to write two-way bindings with NgModel.