當用戶點擊鏈接、按下按鈕或者輸入文字時,這些用戶動作都會產生 DOM 事件。 本章解釋如何使用 Angular 事件綁定語法把這些事件綁定到事件處理器。
你可以使用 Angular 事件綁定機制來響應任何 DOM 事件。 許多 DOM 事件是由用戶輸入觸發(fā)的。綁定這些事件可以獲取用戶輸入。
要綁定 DOM 事件,只要把 DOM 事件的名字包裹在圓括號中,然后用放在引號中的模板語句對它賦值就可以了。
下例展示了一個事件綁定,它實現(xiàn)了一個點擊事件處理器:
Path:"src/app/click-me.component.ts"
<button (click)="onClickMe()">Click me!</button>
等號左邊的 (click)
表示把按鈕的點擊事件作為綁定目標。 等號右邊引號中的文本是模板語句,通過調用組件的 onClickMe
方法來響應這個點擊事件。
寫綁定時,需要知道模板語句的執(zhí)行上下文。 出現(xiàn)在模板語句中的每個標識符都屬于特定的上下文對象。 這個對象通常都是控制此模板的 Angular 組件。 上例中只顯示了一行 HTML,那段 HTML 片段屬于下面這個組件:
Path:"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 調用 ClickMeComponent
的 onClickMe
方法。
DOM 事件可以攜帶可能對組件有用的信息。 本節(jié)將展示如何綁定輸入框的 keyup
事件,在每個敲擊鍵盤時獲取用戶輸入。
下面的代碼監(jiān)聽 keyup
事件,并將整個事件載荷 ($event)
傳給組件的事件處理器。
Path:"src/app/keyup.components.ts (template v.1)"
template: `
<input (keyup)="onKey($event)">
<p>{{values}}</p>
`
當用戶按下并釋放一個按鍵時,觸發(fā) keyup
事件,Angular 在 $event
變量提供一個相應的 DOM 事件對象,上面的代碼將它作為參數(shù)傳給 onKey()
方法。
Path:"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 事件的類型。例如,鼠標事件與輸入框編輯事件包含了不同的信息。
所有標準 DOM 事件對象都有一個 target
屬性, 引用觸發(fā)該事件的元素。 在本例中,target 是 <input>
元素, event.target.value
返回該元素的當前內容。
在組件的 onKey()
方法中,把輸入框的值和分隔符 (|
) 追加組件的 values
屬性。 使用插值來把存放累加結果的 values
屬性回顯到屏幕上。
假設用戶輸入字母“abc”,然后用退格鍵一個一個刪除它們。 用戶界面將顯示:
a | ab | abc | ab | a | |
或者,你可以用
event.key
替代event.target.value
,積累各個按鍵本身,這樣同樣的用戶輸入可以產生:
&
a | b | c | backspace | backspace | backspace |
上例將 $event
轉換為 any
類型。 這樣簡化了代碼,但是有成本。 沒有任何類型信息能夠揭示事件對象的屬性,防止簡單的錯誤。
下面的例子,使用了帶類型方法:
Path:"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
的類型現(xiàn)在是 KeyboardEvent
。 不是所有的元素都有 value
屬性,所以它將 target
轉換為輸入元素。 OnKey
方法更加清晰地表達了它期望從模板得到什么,以及它是如何解析事件的。
類型化事件對象揭露了重要的一點,即反對把整個 DOM 事件傳到方法中,因為這樣組件會知道太多模板的信息。 只有當它知道更多它本不應了解的 HTML 實現(xiàn)細節(jié)時,它才能提取信息。 這就違反了模板(用戶看到的)和組件(應用如何處理用戶數(shù)據(jù))之間的分離關注原則。
下面將介紹如何用模板引用變量來解決這個問題。
還有另一種獲取用戶數(shù)據(jù)的方式:使用 Angular 的模板引用變量。 這些變量提供了從模塊中直接訪問元素的能力。 在標識符前加上井號 (#
) 就能聲明一個模板引用變量。
下面的例子使用了局部模板變量,在一個超簡單的模板中實現(xiàn)按鍵反饋功能。
Path:"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>
標簽中。
這個模板完全是完全自包含的。它沒有綁定到組件,組件也沒做任何事情。
在輸入框中輸入,就會看到每次按鍵時,顯示也隨之更新了。
除非你綁定一個事件,否則這將完全無法工作。
只有在應用做了些異步事件(如擊鍵),Angular 才更新綁定(并最終影響到屏幕)。 本例代碼將
keyup
事件綁定到了數(shù)字 0,這可能是最短的模板語句了。 雖然這個語句不做什么,但它滿足 Angular 的要求,所以 Angular 將更新屏幕。
從模板變量獲得輸入框比通過 $event
對象更加簡單。 下面的代碼重寫了之前 keyup
示例,它使用變量來獲得用戶輸入。
Path:"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 + ' | ';
}
}
這個方法最漂亮的一點是:組件代碼從視圖中獲得了干凈的數(shù)據(jù)值。再也不用了解 $event
變量及其結構了。
(keyup)
事件處理器監(jiān)聽每一次按鍵。 有時只在意回車鍵,因為它標志著用戶結束輸入。 解決這個問題的一種方法是檢查每個 $event.keyCode
,只有鍵值是回車鍵時才采取行動。
更簡單的方法是:綁定到 Angular 的 keyup.enter
模擬事件。 然后,只有當用戶敲回車鍵時,Angular 才會調用事件處理器。
Path:"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; }
}
下面展示了它的工作原理。
前上例中,如果用戶沒有先按回車鍵,而是移開了鼠標,點擊了頁面中其它地方,輸入框的當前值就會丟失。 只有當用戶按下了回車鍵候,組件的 values
屬性才能更新。
下面通過同時監(jiān)聽輸入框的回車鍵和失去焦點事件來修正這個問題。
Path:"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; }
}
現(xiàn)在,在一個微型應用中一起使用它們,應用能顯示一個英雄列表,并把新的英雄加到列表中。 用戶可以通過輸入英雄名和點擊“添加”按鈕來添加英雄。
下面就是“簡版英雄指南”組件。
Path:"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);
}
}
}
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 += (event.target as HTMLInputElement).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);
}
}
}
Angular 還支持被動事件偵聽器。例如,你可以使用以下步驟使?jié)L動事件變?yōu)楸粍颖O(jiān)聽。
(window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];
import './zone-flags';
import 'zone.js/dist/zone'; // Included with Angular CLI.
經過這些步驟,你添加 scroll
事件的監(jiān)聽器時,它就是被動(passive)
的。
newHero
模板變量引用了 <input>
元素。 你可以在 <input>
的任何兄弟或子級元素中引用 newHero
。addHero
,而不要傳遞 newHero
。(blur)
事件被綁定到兩個 JavaScript 語句。 第一句調用 addHero
。第二句 newHero.value=''
在添加新英雄到列表中后清除輸入框。
更多建議: