前言
介紹完 Angular 內兩種表單的不同後,接著實作看看範本驅動表單吧。
需求
Angular 框架支援:
- 雙向資料繫結
- 變更檢測
- 驗證和錯誤處理
而實作過程中,將學會:
- 用元件和範本建構 Angular 表單
- 用 ngModel 建立雙向資料繫結,以讀取和寫入輸入控制元件的值
- 追蹤狀態的變化,並驗證表單控制元件
- 使用特殊的 CSS 類來追蹤控制元件的狀態並給出視覺反饋
- 向用戶顯示驗證錯誤提示,以及啟用/禁用表單控制元件
- 使用範本參考變數在 HTML 元素之間共享資訊
這個範例將:
- 以範本驅動表單的方式實作一個建立英雄的表單
- 必填的欄位在左側有個綠色的豎條,代表這個欄位是必填的
- 如果沒有填寫,表單就會用醒目的樣式把驗證錯誤顯示出來
- 而如果有條件未達成,則無法按下 Submit 按鈕提交
環境準備
建立專案
建立專案的部分就不再贅述了。
建立 hero 的 class
因為每次輸入表單資料時,資料大致上都是固定的,因此可以建立一個 hero 的 class 來處理這些事情。
之後要使用時可以透過 new
將其實例化成物件後,方便我們取用。
hero.ts
1 | export class Hero { |
其中 alterEgo
屬性後面接了 ?
號,代表 alterEgo
屬性不是必須的,呼叫建構函式時這個參數可以省略。
也就是說之後我們可以這樣來建立一個新英雄:
1 | let myHero = new Hero(1, '超級牛', '超級牛來拯救雷', '牛妹妹'); |
建立表單元件
輸入 ng g c HeroForm
建立元件。
接著在 class 內寫一些東西:
hero-form.component
1 | import { Component, OnInit } from '@angular/core'; |
修改 app.module.ts
1 | import { BrowserModule } from '@angular/platform-browser'; |
在這裡要匯入:
- FormsModule
- 把 FormsModule 新增到 ngModule 裝飾器的 imports 陣列中,這樣應用就能訪問範本驅動表單的所有特性,包括 ngModel
修改 app.component.ts
1 | <app-hero-form></app-hero-form> |
別忘了在根元件內把這個子元件載入。
建立初始 HTML 表單範本
修改 hero-form 的元件範本 (Template)
1 | <div class="container"> |
在這裡添加了兩個欄位:
- 姓名 - 必填
- 裏人格 - 非必填
可以發現到這一小段 HTML5 的程式碼,裡面用了一些 Bootstrap4 的 className 。
但這不是必需的,這裡只是因為美觀所以想要使用。
可以透過修改 src/styles.css
引入 Bootstrap4。
1 | /* You can add global styles to this file, and also import other style files */ |
接著運行開發伺服器,觀察一下目前的樣子。
添加能力選單
還記得我們在 HeroFormComponent 的 class 內寫的 powers
陣列嗎?
接下來要使用 ngFor 建立一個下搭式選單。
1 | <div class="form-group"> |
使用 ngModel 進行雙向資料繫結
基礎的表單已經成形,接著我們要將資料雙向繫結到 Input 上。
1 | <div class="container"> |
做到這邊的時候,遇到一個小小的阻礙:
- 當使用 ngModel 時,要記得替 input 補上 name 屬性
NgForm 指令
往 form 標籤中加入 #heroForm="ngForm"
heroForm 變數是一個到 NgForm 指令的參考,它代表該表單的整體。
NgForm 指令為 form 增補了一些額外特性:
- 它會控制那些帶有 ngModel 指令和 name 屬性的元素,監聽他們的屬性(包括其有效性)。
- 它還有自己的 valid 屬性,這個屬性只有在它包含的每個控制元件都有效時才是真。
透過 ngModel 追蹤修改狀態與有效性驗證
現在我們已經可以透過雙向繫結修改資料了,但是還可以透過 ngModel 知道更多資訊:
- 使用者碰過此控制元件嗎?
- 值變化了嗎?
- 資料變得無效了嗎?
ngModel 指令不僅僅追蹤狀態。它還使用特定的 Angular CSS 類來更新控制元件,以反映當前狀態。 可以利用這些 CSS 類來修改控制元件的外觀,顯示或隱藏訊息。
在姓名的 input 標籤上新增名叫 spy
的臨時範本參考變數,然後用這個 spy
來顯示它上面的所有 className。
1 | <div class="form-group"> |
新增用於視覺反饋的自訂 CSS
既然可以追蹤 input 上的 className 狀態,我們就可以自訂一些視覺反饋效果。
hero-form.component.scss
1 | .ng-valid[required], .ng-valid.required { |
甚至可以透過控制 hidden 屬性,自訂一些錯誤訊息。
1 | <div class="form-group"> |
範本參考變數可以訪問 Template 中的 input ,建立 name 的變數並且賦值為 ngModel
。
當這個 input 的驗證是有效的 (valid) 或全新的 (pristine) 時,隱藏訊息。
全新的 (pristine) 意味著從它顯示在表單中開始,使用者還從未修改過它的值。
這樣就完成了視覺反饋效果。
裏人格因為是非必填,所以不需要做處理;下拉式選單則因為設計的關係一定會有值,所以也不需要處理。
新增英雄
在表單的底部放置新增英雄的按鈕,並把它的點選事件繫結到元件上的 newHero 方法。
hero-form.component.html
1 | <button type="button" class="btn btn-primary" (click)="newHero()">新增英雄</button> |
hero-form.component.ts
1 | newHero() { |
觀察看看結果~
使用瀏覽器工具審查這個元素就會發現,這個 name 輸入框並不是全新的。
所以跑出了錯誤提示訊息,但這樣不正確,因為我們並不希望按下新增英雄時跑出錯誤視窗。
發生預期之外的原因是:
- 表單會記得點選新增英雄前輸入的名字,而更換了英雄物件並不會重置 input 的 全新(pristine) 狀態。
所以必須在 newHero() 後補上 heroForm.reset() 重置表單狀態。
因此目前 Template 內的程式碼是這樣的:
1 | <div class="container"> |
使用 ngSubmit 提交該表單
填表完成之後,使用者應該要能提交這個表單。
目前這個表單的提交按鈕位於底部,並沒有在這顆按鈕上綁定任何的點擊事件,但因為有特殊的 type 值 (type=”submit”),所以會觸發表單提交。
但現在這樣僅僅觸發表單提交是沒用的。
要讓它有用,就要把該表單的 ngSubmit 事件屬性繫結到英雄表單元件的 onSubmit() 方法上:
1 | <form (ngSubmit)="onSubmit()" #heroForm="ngForm"> |
這樣就可以在按下提交鈕後觸發寫在 class 內的 onSubmit() 方法了。
提升使用者體驗
可以進一步的把表單的總體有效性透過 heroForm 變數繫結到此按鈕的 disabled 屬性,這樣能讓使用者明白如果沒有填寫姓名是不能提交的。
1 | <button type="submit" [disabled]="!heroForm.form.valid" class="btn btn-success">提交</button> |
切換兩個表單區域
在我們按下提交後,可以將表單利用先前設置好的屬性 submitted
來控制隱藏或顯示。
建立提交後的顯示區塊,並且利用 submitted 控制隱藏或顯示
1 | <div class="container"> |
一開始屬性 submitted
為 false
,顯示輸入表單
- 當按下提交後,觸發 onSubmit() 將
submitted
修改為true
- 輸入表單關閉,顯示提交後的區塊
- 當按下編輯按鈕時,再度將
submitted
修改為false
,隱藏提交後的區塊並顯示輸入表單。
如此我們就完成了範本驅動表單 (Template-Driven Forms) 的簡單範例。