Angular9 用戶輸入

2020-06-30 18:01 更新

用戶輸入

當用戶點擊鏈接、按下按鈕或者輸入文字時,這些用戶動作都會產生 DOM 事件。 本章解釋如何使用 Angular 事件綁定語法把這些事件綁定到事件處理器。

綁定到用戶輸入事件

你可以使用 Angular 事件綁定機制來響應任何 DOM 事件。 許多 DOM 事件是由用戶輸入觸發(fā)的。綁定這些事件可以獲取用戶輸入。

要綁定 DOM 事件,只要把 DOM 事件的名字包裹在圓括號中,然后用放在引號中的模板語句對它賦值就可以了。

下例展示了一個事件綁定,它實現(xiàn)了一個點擊事件處理器:

Path:"src/app/click-me.component.ts"

  1. <button (click)="onClickMe()">Click me!</button>

等號左邊的 (click) 表示把按鈕的點擊事件作為綁定目標。 等號右邊引號中的文本是模板語句,通過調用組件的 onClickMe 方法來響應這個點擊事件。

寫綁定時,需要知道模板語句的執(zhí)行上下文。 出現(xiàn)在模板語句中的每個標識符都屬于特定的上下文對象。 這個對象通常都是控制此模板的 Angular 組件。 上例中只顯示了一行 HTML,那段 HTML 片段屬于下面這個組件:

Path:"src/app/click-me.component.ts"

  1. @Component({
  2. selector: 'app-click-me',
  3. template: `
  4. <button (click)="onClickMe()">Click me!</button>
  5. {{clickMessage}}`
  6. })
  7. export class ClickMeComponent {
  8. clickMessage = '';
  9. onClickMe() {
  10. this.clickMessage = 'You are my hero!';
  11. }
  12. }

當用戶點擊按鈕時,Angular 調用 ClickMeComponentonClickMe 方法。

通過 $event 對象取得用戶輸入

DOM 事件可以攜帶可能對組件有用的信息。 本節(jié)將展示如何綁定輸入框的 keyup 事件,在每個敲擊鍵盤時獲取用戶輸入。

下面的代碼監(jiān)聽 keyup 事件,并將整個事件載荷 ($event) 傳給組件的事件處理器。

Path:"src/app/keyup.components.ts (template v.1)"

  1. template: `
  2. <input (keyup)="onKey($event)">
  3. <p>{{values}}</p>
  4. `

當用戶按下并釋放一個按鍵時,觸發(fā) keyup 事件,Angular 在 $event 變量提供一個相應的 DOM 事件對象,上面的代碼將它作為參數(shù)傳給 onKey() 方法。

Path:"src/app/keyup.components.ts (class v.1)"

  1. export class KeyUpComponent_v1 {
  2. values = '';
  3. onKey(event: any) { // without type info
  4. this.values += event.target.value + ' | ';
  5. }
  6. }

$event 對象的屬性取決于 DOM 事件的類型。例如,鼠標事件與輸入框編輯事件包含了不同的信息。

所有標準 DOM 事件對象都有一個 target 屬性, 引用觸發(fā)該事件的元素。 在本例中,target 是 <input> 元素, event.target.value 返回該元素的當前內容。

在組件的 onKey() 方法中,把輸入框的值和分隔符 (|) 追加組件的 values 屬性。 使用插值來把存放累加結果的 values 屬性回顯到屏幕上。

假設用戶輸入字母“abc”,然后用退格鍵一個一個刪除它們。 用戶界面將顯示:

  1. a | ab | abc | ab | a | |

或者,你可以用 event.key 替代 event.target.value,積累各個按鍵本身,這樣同樣的用戶輸入可以產生:

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

$event的類型

上例將 $event 轉換為 any 類型。 這樣簡化了代碼,但是有成本。 沒有任何類型信息能夠揭示事件對象的屬性,防止簡單的錯誤。

下面的例子,使用了帶類型方法:

Path:"src/app/keyup.components.ts (class v.1 - typed )"

  1. export class KeyUpComponent_v1 {
  2. values = '';
  3. onKey(event: KeyboardEvent) { // with type info
  4. this.values += (event.target as HTMLInputElement).value + ' | ';
  5. }
  6. }

$event 的類型現(xiàn)在是 KeyboardEvent。 不是所有的元素都有 value 屬性,所以它將 target 轉換為輸入元素。 OnKey 方法更加清晰地表達了它期望從模板得到什么,以及它是如何解析事件的。

傳入 $event 是靠不住的做法

類型化事件對象揭露了重要的一點,即反對把整個 DOM 事件傳到方法中,因為這樣組件會知道太多模板的信息。 只有當它知道更多它本不應了解的 HTML 實現(xiàn)細節(jié)時,它才能提取信息。 這就違反了模板(用戶看到的)和組件(應用如何處理用戶數(shù)據)之間的分離關注原則。

下面將介紹如何用模板引用變量來解決這個問題。

從一個模板引用變量中獲得用戶輸入

還有另一種獲取用戶數(shù)據的方式:使用 Angular 的模板引用變量。 這些變量提供了從模塊中直接訪問元素的能力。 在標識符前加上井號 (#) 就能聲明一個模板引用變量。

下面的例子使用了局部模板變量,在一個超簡單的模板中實現(xiàn)按鍵反饋功能。

Path:"src/app/loop-back.component.ts"

  1. @Component({
  2. selector: 'app-loop-back',
  3. template: `
  4. <input #box (keyup)="0">
  5. <p>{{box.value}}</p>
  6. `
  7. })
  8. export class LoopbackComponent { }

這個模板引用變量名叫 box,在 <input> 元素聲明,它引用 <input> 元素本身。 代碼使用 box 獲得輸入元素的 value 值,并通過插值把它顯示在 <p> 標簽中。

這個模板完全是完全自包含的。它沒有綁定到組件,組件也沒做任何事情。

在輸入框中輸入,就會看到每次按鍵時,顯示也隨之更新了。

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

只有在應用做了些異步事件(如擊鍵),Angular 才更新綁定(并最終影響到屏幕)。 本例代碼將 keyup 事件綁定到了數(shù)字 0,這可能是最短的模板語句了。 雖然這個語句不做什么,但它滿足 Angular 的要求,所以 Angular 將更新屏幕。

從模板變量獲得輸入框比通過 $event 對象更加簡單。 下面的代碼重寫了之前 keyup 示例,它使用變量來獲得用戶輸入。

Path:"src/app/keyup.components.ts (v2)"

  1. @Component({
  2. selector: 'app-key-up2',
  3. template: `
  4. <input #box (keyup)="onKey(box.value)">
  5. <p>{{values}}</p>
  6. `
  7. })
  8. export class KeyUpComponent_v2 {
  9. values = '';
  10. onKey(value: string) {
  11. this.values += value + ' | ';
  12. }
  13. }

這個方法最漂亮的一點是:組件代碼從視圖中獲得了干凈的數(shù)據值。再也不用了解 $event 變量及其結構了。

按鍵事件過濾(通過 key.enter)

(keyup) 事件處理器監(jiān)聽每一次按鍵。 有時只在意回車鍵,因為它標志著用戶結束輸入。 解決這個問題的一種方法是檢查每個 $event.keyCode,只有鍵值是回車鍵時才采取行動。

更簡單的方法是:綁定到 Angular 的 keyup.enter 模擬事件。 然后,只有當用戶敲回車鍵時,Angular 才會調用事件處理器。

Path:"src/app/keyup.components.ts (v3)"

  1. @Component({
  2. selector: 'app-key-up3',
  3. template: `
  4. <input #box (keyup.enter)="onEnter(box.value)">
  5. <p>{{value}}</p>
  6. `
  7. })
  8. export class KeyUpComponent_v3 {
  9. value = '';
  10. onEnter(value: string) { this.value = value; }
  11. }

下面展示了它的工作原理。

失去焦點事件 (blur)

前上例中,如果用戶沒有先按回車鍵,而是移開了鼠標,點擊了頁面中其它地方,輸入框的當前值就會丟失。 只有當用戶按下了回車鍵候,組件的 values屬性才能更新。

下面通過同時監(jiān)聽輸入框的回車鍵和失去焦點事件來修正這個問題。

Path:"src/app/keyup.components.ts (v4)"

  1. @Component({
  2. selector: 'app-key-up4',
  3. template: `
  4. <input #box
  5. (keyup.enter)="update(box.value)"
  6. (blur)="update(box.value)">
  7. <p>{{value}}</p>
  8. `
  9. })
  10. export class KeyUpComponent_v4 {
  11. value = '';
  12. update(value: string) { this.value = value; }
  13. }

結合使用

現(xiàn)在,在一個微型應用中一起使用它們,應用能顯示一個英雄列表,并把新的英雄加到列表中。 用戶可以通過輸入英雄名和點擊“添加”按鈕來添加英雄。

下面就是“簡版英雄指南”組件。

Path:"src/app/little-tour.component.ts"

  1. @Component({
  2. selector: 'app-little-tour',
  3. template: `
  4. <input #newHero
  5. (keyup.enter)="addHero(newHero.value)"
  6. (blur)="addHero(newHero.value); newHero.value='' ">
  7. <button (click)="addHero(newHero.value)">Add</button>
  8. <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
  9. `
  10. })
  11. export class LittleTourComponent {
  12. heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  13. addHero(newHero: string) {
  14. if (newHero) {
  15. this.heroes.push(newHero);
  16. }
  17. }
  18. }

源代碼

  1. Path:"src/app/click-me.component.ts"

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-click-me',
  4. template: `
  5. <button (click)="onClickMe()">Click me!</button>
  6. {{clickMessage}}`
  7. })
  8. export class ClickMeComponent {
  9. clickMessage = '';
  10. onClickMe() {
  11. this.clickMessage = 'You are my hero!';
  12. }
  13. }

  1. Path:"src/app/keyup.components.ts"

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-key-up1',
  4. template: `
  5. <input (keyup)="onKey($event)">
  6. <p>{{values}}</p>
  7. `
  8. })
  9. export class KeyUpComponent_v1 {
  10. values = '';
  11. /*
  12. onKey(event: any) { // without type info
  13. this.values += event.target.value + ' | ';
  14. }
  15. */
  16. onKey(event: KeyboardEvent) { // with type info
  17. this.values += (event.target as HTMLInputElement).value + ' | ';
  18. }
  19. }
  20. //////////////////////////////////////////
  21. @Component({
  22. selector: 'app-key-up2',
  23. template: `
  24. <input #box (keyup)="onKey(box.value)">
  25. <p>{{values}}</p>
  26. `
  27. })
  28. export class KeyUpComponent_v2 {
  29. values = '';
  30. onKey(value: string) {
  31. this.values += value + ' | ';
  32. }
  33. }
  34. //////////////////////////////////////////
  35. @Component({
  36. selector: 'app-key-up3',
  37. template: `
  38. <input #box (keyup.enter)="onEnter(box.value)">
  39. <p>{{value}}</p>
  40. `
  41. })
  42. export class KeyUpComponent_v3 {
  43. value = '';
  44. onEnter(value: string) { this.value = value; }
  45. }
  46. //////////////////////////////////////////
  47. @Component({
  48. selector: 'app-key-up4',
  49. template: `
  50. <input #box
  51. (keyup.enter)="update(box.value)"
  52. (blur)="update(box.value)">
  53. <p>{{value}}</p>
  54. `
  55. })
  56. export class KeyUpComponent_v4 {
  57. value = '';
  58. update(value: string) { this.value = value; }
  59. }

  1. Path:"src/app/loop-back.component.ts"

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-loop-back',
  4. template: `
  5. <input #box (keyup)="0">
  6. <p>{{box.value}}</p>
  7. `
  8. })
  9. export class LoopbackComponent { }

  1. Path:"src/app/little-tour.component.ts"

  1. import { Component } from '@angular/core';
  2. @Component({
  3. selector: 'app-little-tour',
  4. template: `
  5. <input #newHero
  6. (keyup.enter)="addHero(newHero.value)"
  7. (blur)="addHero(newHero.value); newHero.value='' ">
  8. <button (click)="addHero(newHero.value)">Add</button>
  9. <ul><li *ngFor="let hero of heroes">{{hero}}</li></ul>
  10. `
  11. })
  12. export class LittleTourComponent {
  13. heroes = ['Windstorm', 'Bombasto', 'Magneta', 'Tornado'];
  14. addHero(newHero: string) {
  15. if (newHero) {
  16. this.heroes.push(newHero);
  17. }
  18. }
  19. }

Angular 還支持被動事件偵聽器。例如,你可以使用以下步驟使?jié)L動事件變?yōu)楸粍颖O(jiān)聽。

  1. 在 src 目錄下創(chuàng)建一個 "zone-flags.ts" 文件。

  1. 往這個文件中添加如下語句。

  1. (window as any)['__zone_symbol__PASSIVE_EVENTS'] = ['scroll'];

  1. 在 "src/polyfills.ts" 文件中,導入 "zone.js" 之前,先導入新創(chuàng)建的 "zone-flags" 文件。

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

經過這些步驟,你添加 scroll 事件的監(jiān)聽器時,它就是被動(passive)的。

小結

  • 使用模板變量來引用元素 — newHero 模板變量引用了 <input> 元素。 你可以在 <input> 的任何兄弟或子級元素中引用 newHero。

  • 傳遞數(shù)值,而非元素 — 獲取輸入框的值并將它傳給組件的 addHero,而不要傳遞 newHero

  • 保持模板語句簡單 — (blur) 事件被綁定到兩個 JavaScript 語句。 第一句調用 addHero。第二句 newHero.value='' 在添加新英雄到列表中后清除輸入框。
以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號