iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 6
2
Modern Web

Angular 全集中筆記系列 第 6

第 6 型 - 單向資料繫結 (One-way Binding)

  • 分享至 

  • xImage
  •  

在開發應用程式時,常會需要將資料顯示在頁面中,或是在使用者操作後更改資料。為了實作此種需求,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;
}

利用內嵌繫結 (Interpolation) 實作顯示待辦事項主旨

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) 所繫結的對象外,不應該去改變應用程式中的其他狀態。

利用事件繫結 (Event Binding) 實作設定待辦事項的狀態

若要讓使用者透過按鈕點選來設定待辦事項的狀態,就需要去監控按鈕點選的事件,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>

利用屬性繫結 (Property Binding / Attribute Binding) 控制編輯按鈕是否可使用

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 繫結的語法較為簡單與直觀,且有較高的效能,因此在實務上較常使用。

Property Binding

利用樣式繫結 (Style Binding) 與類別繫結 (Class Binding) 變更狀態文字顏色

要依不同的狀態來變更待辦事項文字的顏色,可以利用樣式繫結 (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 Binding

上述是針對單個樣式進行繫結,若要切換多個樣式時,則會直行利用 [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 Binding

當要繫結多個樣式類別,則會使用 [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() 裝飾器來實作元件互動方式。


上一篇
第 5 型 - Angular 元件
下一篇
第 7 型 - 元件互動與雙向資料繫結 (Two-way Binding)
系列文
Angular 全集中筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
BA
iT邦新手 5 級 ‧ 2022-11-03 10:07:52

Angular 14 補充:

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

我要留言

立即登入留言