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