如何使用 Angular 實作下拉選單?
下拉選單為常用的使用者介面,該如何優雅地將資料綁定在元件上,並且優雅地取得使用者的選擇資料呢?
Version
Angular CLI 1.1.2
Angular 4.2.3
Introudction
將實作出一下拉選單,其顯示資料來自於資料綁定,當使用者有不同的選擇,會將其值顯示在 select 下方。
將示範 3 種實作方式:
- 使用 DOM event
- 使用 Template Reference Variable
- 使用 Two-Way Binding
使用 DOM event 物件
src/app/app.component.html1 1GitHub Commit : app.component.html
1 | <select (change)="onChange($event)"> |
第 2 行
1 | <option *ngFor="let cloud of clouds" [value]="cloud.id">{{ cloud.name }}</option> |
使用 *ngFor
這個 structure directive 重複顯示 <option>
,其中 clouds
型別為 Cloud[]
,每一筆資料 cloud
為 Cloud
ViewModel,有 name
與 id
兩個欄位,稍後會看到 Cloud
ViewModel 的定義。
第 1 行
1 | <select (change)="onChange($event)"> |
(change)
為 event binding,當 change event 被觸發時,執行 onChange()
event handler。
$event
為 event object,若 event 為原生的 DOM event,則 $event
為 DOM event object,擁有 target
與 target.value
等 property。
將 $event
以參數傳進 onChange()
。
在原生 JavaScript 中,
event
物件可直接使用,不需要前面加上$
,但在 Angular 的 HTML template 中,若要使用 event object,Angular 規定要從event
改成$event
,Angular 在底層另有處理,暫時只能當語法背起來。
第 5 行
1 | {{ selectedId }} |
顯示 select 所選擇的 value,即 cloud.id
。
src/app/app.component.ts2 2GitHub Commit : app.component.ts
1 | import { Component } from '@angular/core'; |
第 10 行
1 | clouds: Cloud[] = [ |
clouds
為 select 欲作 data binding 的資料,實務上此資料會透過 API 取得,在此為了簡化起見,先直接 hardcode 一個陣列。
16 行
1 | selectedId: number; |
宣告 selectedCloudId
為 number
型別,雖然也可以宣告為 string
,但因為 id
在 cloud
宣告為 number
型別,所以 selectedId
也宣告為 number
型別較合適。
18 行
1 | onChange(event: Event) { |
onChange
為 select change event 的 event handler,其中 event
為 HTML template 傳進來的 $event
,型別為 Event
。
event.target
在 lib.es6.d.ts
定義的型別為 EventTarget
,但我們知道其本質型別為 HTMLSelectElement
,因此使用 type assertion 加上 <HTMLSelectElement>
將 event target
轉型成 HTMLSelectElement
,則 intellisense 就會有 value
可選,不過 value
的型別為 string
,因此要再加上 +
將 string
轉成 number
。
src/app/cloud.ts3 3GitHub Commit : cloud.ts
1 | export interface Cloud { |
宣告 Cloud
的 ViewModel,id
為 number
,name
為 string
。
使用 Template Reference Variable
src/app/app.component.html4 4GitHub Commit : app.component.html
1 | <select (change)="onChange(mySelect)" #mySelect> |
第 1 行
1 | <select (change)="onChange(mySelect)" #mySelect> |
原本 onChange()
是傳進 $event
,這裡改傳 mySelect
。
#
為 template reference variable,我們可以在 HTML template 內,對任意 HTML element 加上 #
與變數名稱,Angular 會自動幫我們對該 element 建立物件。
src/app/app.component.ts4 4GitHub Commit : app.component.ts
1 | import { Component } from '@angular/core'; |
18 行
1 | onChange(element: HTMLSelectElement) { |
onChange()
改接收 template reference variable 後,因為我們確定 onChange()
為 select 的 event handler,所以傳進的 element 型別必為 HTMLSelectElement
。
由於 element.value
型別為 string
,必須加上 +
轉型為 number
。
使用 Two-Way Binding
src/app/app.module.ts5 5GitHub Commit : app.module.ts
1 | import { BrowserModule } from '@angular/platform-browser'; |
11 行
1 | imports: [ |
要使用 two-way binding,必須在 AppModule
手動 import FormsModule
。
src/app/app.component.html5 5GitHub Commit : app.component.html
1 | <select [(ngModel)]="selectedId"> |
使用 [(ngModel)]
直接 two-way binding 到 selectedId
,其他都可以拿掉。
src/app/app.component.ts6 6GitHub Commit : app.component.ts
1 | import {Component} from '@angular/core'; |
由於使用了 two-way binding,所有的 event handler 也可以拿掉,當 select 選擇改變時,自動會改變 selectedId
。
實務上該使用哪種寫法?
雖然表面上 two-way binding 的寫法最精簡,若以物件導向與強型別觀點,template reference variable 的寫法語意較佳:
- 明確將物件傳入 event handler 當中。
- Event handler 的參數可明確宣告物件型別加以檢查。
- 取得物件的值較直觀,不必搭配 type assertion。
Conclusion
- 仍然可以在 HTML template 使用 DOM 的 event 物件,但必須加上
event
前面加上$
變成$event
。 - Template reference variable 技巧在實務上常常使用,可隨時在 HTML template 中將 HTML element 宣告成變數傳入 event handler。
- Two-way binding 實際上是個 syntax sugur,Angular 會展開實作
(ngModelChange)
event handler。
Sample Code
完整的範例可以在我的 GitHub 上找到。