在開發應用程式時,常會需要將資料顯示在頁面中,或是在使用者操作後更改資料。為了實作此種需求,Angular 提供了資料繫結 (Data Binding) 的方法。這一篇就利用這些資料繫結方法來實作下列待辦事項元件的需求。
在實作需求前,先定義待辦事項類別,用以記錄所設定的資料狀態。如同建立元件,在終端機中執行 ng g class model/task --skipTests
命令來建立 Task 類別 (可以縮寫為 ng g cl model/task --skipTests
)。
import { TaskState } from "../enum/task-state.enum";
export class Task {
constructor(
public subject: string,
public state: TaskState = TaskState.None
) {}
}
因為待辦事項的狀態包含了未開工、工作中與已完成等固定選項,為了減少開發時字串輸入錯誤,因此也針對狀態定義一個列舉型別,故執行 ng g enum enum/task-state
建立列舉檔案 (也可以縮寫為 ng g e enum/task-state
)。
export enum TaskState {
None,
Doing,
Finish,
}
- 因目前不會針對 Task 類別進行單元測試,故加入 --skipTests 參數來不產生 task.spec.ts 檔案。
- 除了利用列舉來定義待辦事項的狀態,也可以利用 TypeScript 的聯集類別 (Union Type) 定義,因此在 Task 類別中,就會定義成
state: 'None' | 'Doing' | 'Finish'。
另外,為了讓實作結果能更明顯的效果,在 task.component.css 檔案中,加入待辦事項的顯示樣式。
div.card {
margin: 5px;
font-size: 14pt;
line-height: 2em;
border: solid 2px #bbb;
}
div.card div.content {
padding: 10px;
border-bottom: solid 1px #ccc;
display: flex;
justify-content: space-between;
}
div.card div.button {
padding-left: 10px;
padding-right: 10px;
display: flex;
justify-content: space-between;
}
div.card div.button button {
margin-right: 5px;
}
Angular 的內嵌繫結 (Interpolation) 可以把資料值繫結在 HTML 標籤上,使用方式就是在 HTML 標籤加上 {{ }}
,雙括號內則指定要繫結的元件屬性;因此可以利用此方法來將待辦事項主旨繫結至頁面上。
首先,先在 task.component.ts 內,加入 task
屬性,並在 OnInit
方法實體化。
import { Task } from "../../model/task";
export class TaskComponent implements OnInit {
task: Task;
ngOnInit(): void {
this.task = new Task("頁面需要顯示待辦事項主旨");
}
}
接著,在 task.component.html 中,加入 <div>
標籤,並利用內嵌繫結 (Interpolation) 的方式,將 task.subject
屬性繫結至頁面中。
<div class="card">
<div class="content">{{ task.subject }}</div>
</div>
最後,可以利用 ng serve
命令啟動 Angular 應用程式來確認結果。
在 {{ }}
內除了指定元件的屬性外,也可以指定元件的方法。在 task.component.ts 加入方法 getStateDesc()
來取得要顯示的狀態文字。
import { TaskState } from "../../enum/task-state.enum";
export class TaskComponent implements OnInit {
getStateDesc(): string {
switch (this.task.state) {
case TaskState.None:
return "未完成";
case TaskState.Doing:
return "進行中";
case TaskState.Finish:
return "已完成";
}
}
}
最後,在 task.component.html 檔案將 getStateDesc()
方法繫結至頁面上,就可以在頁面上顯示狀態。
<div class="card">
<div class="content">
<span>{{ task.subject }}</span>
<span>{{ getStateDesc() }}</span>
</div>
</div>
Angular 官方建議在使用內嵌繫結時,最好是簡單與執行迅速,以增加程式的可讀性,且不會讓使用者覺得頁面顯示上遲緩。另外,基於從資料繫結至頁面的單向資料流策略,除了內嵌繫結 (Interpolation) 所繫結的對象外,不應該去改變應用程式中的其他狀態。
若要讓使用者透過按鈕點選來設定待辦事項的狀態,就需要去監控按鈕點選的事件,Angular 提供了事件繫結 (Event Binding) 來監控這種使用者的動作。其語法是 (event)="method()"
,在等號左邊指定目標事件,而右邊則指定在事件觸發後要執行的方法。
首先,在 task.component.ts 建立 onSetTaskState()
方法,用來依傳入的狀態值設定待辦事項的狀態;因為在 html 頁面中需要使用到 TaskState
列舉型別,所以也需要將其建立為元件屬性。
import { TaskState } from "../../enum/task-state.enum";
export class TaskComponent implements OnInit {
TaskState = TaskState;
onSetTaskState(state: TaskState): void {
this.task.state = state;
}
}
接著,在 task.component.html 中加入三個按鈕,並繫結 click
事件,將所代表的狀態值傳入 onSetTaskState()
方法。如此一來,就可以讓使用者利用此三個按鈕來變更待辦事項的狀態。
<div class="card">
<div class="content">
<span>{{ task.subject }}</span>
<span>{{ getStateDesc() }}</span>
</div>
<div class="button">
<span>
<button type="button" (click)="onSetTaskState(TaskState.None)">
未完成
</button>
<button type="button" (click)="onSetTaskState(TaskState.Doing)">
進行中
</button>
<button type="button" (click)="onSetTaskState(TaskState.Finish)">
已完成
</button>
</span>
</div>
</div>
Angular 提供的屬性繫結來將資料繫結在頁面元素中。但需要注意的是,在網頁應用程式開發中,Attribute 與 Property 兩者雖然在中文都稱屬性,但前者是 HTML 所定義的,後者則是文件物件模型 (Document Object Model, DOM) 的節點屬性。而這兩者並非是互相對應的,且名稱也不一定會相同;例如,<td>
標籤內的 colspan
屬性 (Attribute) 所對應的 DOM 屬性 (Property) 是 HTMLTableCellElement.colSpan
,因此在使用的時候還是先查詢一下 MDN 文件。
在 task.component.html 檔案加入編輯按鈕,並依事項狀態來設定 disabled
。在語法上,Property 繫結是以 [property]="value"
表示;若要使用 Attribute 繫結,則以 [attr.name]="value"
表示。
<div class="content">
<span>
{{ task.subject }}
<button type="button" [disabled]="task.state === TaskState.Finish">
編輯
</button>
</span>
<span>{{ getStateDesc() }}</span>
</div>
若要在此需求下使用 Attribute 繫結,需要注意的是:按鈕是否可以使用取決於該值是否為 null,因此應使用的語法為:
<button
type="button"
[attr.disabled]="task.state === TaskState.Finish ? 'disabled' : null"
>
編輯
</button>
由於 Property 繫結的語法較為簡單與直觀,且有較高的效能,因此在實務上較常使用。
要依不同的狀態來變更待辦事項文字的顏色,可以利用樣式繫結 (Style Binding) 與類別繫結 (Class Binding) 兩種繫結方式來實作。
樣式繫結 (Style Binding) 是針對 HTML 中 style
屬性的 CSS 樣式進行資料繫結,其語法是 [style.css 樣式名]="value"
。因此,可以先在 task.component.ts 加入 getStateColor()
來取得要使用的顏色;然後在 task.component.html 中針對顏色樣式設定繫結。
export class TaskComponent implements OnInit {
getStateColor(): string {
switch (this.task.state) {
case TaskState.Doing:
return "green";
case TaskState.Finish:
return "blue";
}
}
}
<div class="content">
<span>
{{ task.subject }}
<button type="button" [disabled]="task.state === TaskState.Finish">
編輯
</button>
</span>
<span [style.color]="getStateColor()">{{ getStateDesc() }}</span>
</div>
利用 Chrome DevTools 檢視執行的頁面,可以看出 [style.color]
樣式繫結的結果,是設定狀態文字標籤的 style
屬性。
上述是針對單個樣式進行繫結,若要切換多個樣式時,則會直行利用 [style]="styleExpr"
語法繫結,此時賦予的繫結資料會是一系列樣式所組成的字串,或是一個以樣式名為主鍵、以樣式值為值的物件。另外,當要繫結寬度 (width) 樣式時,除了利用 [style.width]="value"
語法,也可以使用 [style.width.px]="value"
語法來指定所需要的單位,前者繫結的值會是字串型別,後者則為數字型別。
export class TaskComponent implements OnInit {
getStateStyle(): string {
switch (this.task.state) {
case TaskState.Doing:
return "color: green";
case TaskState.Finish:
return "color: blue";
}
}
}
除了樣式繫結 (Style Binding) 之外,類別繫結 (Class Binding) 也可以實作變更狀態的文字顏色,此方式是針對 HTML 中 class
屬性進行繫結。在 task.component.css 檔案中定義樣式內容,然後在 task.component.html 中進行類別繫結。
span.doing {
color: green;
}
span.finish {
color: blue;
}
<div class="content">
<span>
{{ subject }}
<button type="button" [disabled]="task.state === TaskState.Finish">
編輯
</button>
</span>
<span
[class.doing]="task.state === TaskState.Doing"
[class.finish]="task.state === TaskState.Finish"
>
{{ getStateDesc() }}
</span>
</div>
同樣地,利用 Chrome DevTools 檢視執行的頁面,可以看出 [class.doing]
樣式繫結的結果,是設定狀態文字標籤的 class
屬性。
當要繫結多個樣式類別,則會使用 [class]="classExpr"
語法,此時繫結的資料可以是以空格分隔的樣式類別字串,例如 [class]="classA classB"
;也可以是一字串陣列,例如 [class]="['classA', 'classB']
。此方法也可以實作依條件繫結樣式類別,其值會是以樣式類別名稱為主鍵、布林值為值的物件,因此上述的需求案例可以下列的語法實作。
<span
[class]="{ doing: task.state === TaskState.Doing, finish: task.state === TaskState.Finish }"
>
{{ getStateDesc() }}
</span>
這一篇利用了 Angular 提供的四種單向繫結實作了待辦事項檢視元件,而實作的程式碼可至 GitHub 參考。目前待辦事項的資料設定在 TaskComponent 內,但實務上資料較常是從元件外傳入,因此接下來就利用 Angular 提供的 @Input()
與 @Output()
裝飾器來實作元件互動方式。
ng g class model/task --skipTests
ng g cl model/task --skipTests
Error: Unknown argument: skipTests
換成
ng g class model/task --skip-tests
ng g cl model/task --skip-tests