iT邦幫忙

2021 iThome 鐵人賽

DAY 29
0
Modern Web

網站一條龍 - 從架站到前端系列 第 29

[Day29] Template Driven Form

昨天我們完成了 service 的部分,已經可以用程式碼打 API 來做 CRUD,但是我們還沒有準備表單的 component,今天,我們就要來幫我們的 Angular app 補上表單的頁面。

Angular 的表單有兩種,一種叫 templae driven form(範本驅動表單),另一種叫 reactive form (響應式表單)。template driven form 主要在 HTML 中使用範本語法、簡單易學;而reactive form 主要用 component.ts 裡的程式控制表單的樹狀物件,比較進階,但是速度快、且更適合用來製作複雜的表單。由於本系列只介紹入門款,所以我們今天只會介紹 template driven form,若想學習 reative form 可以參考這篇文章,文章寫得非常詳細,雖然它是 Angular 5 的,但大部分觀念與語法都通用。

Template Driven Form

首先我們一樣新增一個表單的 component,這邊筆者命名為 ironman-form-add。

要在我們的 component 中使用 template driven form 很簡單,只需要以下幾個步驟

  1. 引用 FromsModule
  2. 建立 HTML 表單,並賦予這個表單一個代表 NgForm 的範本參考變數(template reference variable)
  3. 用 ngModel 把 HTML 輸入與 data model 雙向綁定
  4. 處理提交的工作

首先,我們到 app.module.ts 中引入 FromsModule

// app.module.ts
// ...
import { FormsModule } from '@angular/forms';
// ...
  imports: [
    // ...
    FormsModule,
    // ...
  ]

接著,到 component.ts 建立要用來儲存資料的 data model,然後寫一下提交表單時的處理邏輯

// ironman-form-add.component.ts
userModel: IronmanUser = {
    userId: 0,
    userName: '',
    email: '',
    verified: false
};
// ...
// ...
onSubmit(form: NgForm): void {
    if (form.valid) { // 驗證 OK 才打 API
      this.ironmanService
          .addUser(this.userModel)
          .subscribe(resp => {
            this.submitted = true;
          });
    }
}

再來到 component.html 中建立表單,然後給一個範本參考變數、以及處理資料的繫結

<!-- ironman-form-add.component.html -->
<form (ngSubmit)="onSubmit(formInstance)" #formInstance="ngForm">
    <label for="userName">UserName</label>
    <input type="text" id="userName" [(ngModel)]="userModel.userName" name="userName">
    <br>
    <label for="email">Email</label>
    <input type="email" id="email" [(ngModel)]="userModel.email" name="email">
    <br>
    <button type="submit">新增</button>
</form>

這裡稍微說明一下上面的程式

  • 當提交按鈕被點擊時會觸發 ngSubmit 事件,我們使用事件繫結指派 onSubmit() 這個 function 來處理提交表單的邏輯
  • 我們用井字號宣告了一個範本參考變數(#formInstance),這個變數儲存了 "ngForm",ngForm 就是 Angular 表單的實體
  • 在同個 component.html 檔案中,可以從任何地方存取 #formInstance,例如我們把 #formInstance 當作參數,把整個 Angular 表單的實體傳給 onSubmit()
  • ngModel 是一個內建的 Directive(指令),他會幫我們替 html input 與 component.ts 中的 model 做雙向繫結
  • 請注意使用 template driven form 的時候,html input 一定要加 name 屬性否則會出錯。

簡單的幾行程式碼,我們就完成新增使用者的表單了~~

表單驗證

剛剛一切從簡,目的只在用最少的程式碼讓我們的前端 app 動起來,不過像剛剛那樣陽春的表單當然是不合格,因為我們什麼東西都沒驗證,隨便亂打都能把資料打給 API,錯了自己都不知道。所以現在我們要來介紹一下簡單的表單驗證。

用 Angular 做基礎的驗證也非常簡單,只要在 html input 再加上代表驗證規則的屬性(attribute)就可以,Angular 會在使用者輸入資料的時候自動幫我們做驗證。

    <input type="text" id="userName" [(ngModel)]="userModel.userName" name="userName" 
        required minlength="2"> <!-- 加了「必填」和「最小長度」的驗證 -->
    <input type="email" id="email" [(ngModel)]="userModel.email" name="email" 
        required email> <!-- 加了「必填」和「email 格式」的驗證 -->

僅僅是加了這幾個簡單的驗證屬性,我們就能從 NgForm 實體中看到是否有錯誤發生
https://ithelp.ithome.com.tw/upload/images/20210929/20140664I7RKRkmeZL.png

內建的驗證規則與它們所觸發的 error 屬性名稱請參閱官方文件

就如我們剛剛提到的,這個代表 Angular 表單的範本參考變數,可以在同個 component.html 的任何地方存取,所以我們就可以用他搭配 *ng-if 來顯示錯誤提示

 <div *ngIf="formInstance.invalid && formInstance.dirty">
      <p *ngIf="formInstance.form.controls.userName.errors?.required">
        使用者名稱為必填
      </p>
      <p *ngIf="formInstance.form.controls.userName.errors?.minlength">
        使用者名稱最少需要{{formInstance.form.controls.userName.errors?.minlength.requiredLength}}字元
      </p>
      <p *ngIf="formInstance.form.controls.email.errors?.required">
        email 為必填
      </p>
      <p *ngIf="formInstance.form.controls.email.errors?.email">
        email 格式錯誤
      </p>
    </div>

用範本參考變數讓程式碼看起來簡潔一點點(對,只有一點點)

上面的程式雖然已經能夠運作,但是在驗證那塊的程式碼實在有點太長,看起來很醜。如果我們要讓這個驗證看起來簡潔一些,可以為 html input 也宣告一個範本參考變數,然後把 ngModel 賦值給這個變數,這樣這個變數就會指向一個 FormControl (表單控制項,組成 Angular 表單的元件),我們再從這個 FormControl 取得錯誤資訊。

修改後的程式碼如下

<form (ngSubmit)="onSubmit(formInstance)" #formInstance="ngForm">
    <label for="userName">UserName</label>
    <input type="text" id="userName" [(ngModel)]="userModel.userName" name="userName" 
        required minlength="2"
      #userInput="ngModel">
    <br>
    <label for="email">Email</label>
    <input type="email" id="email" [(ngModel)]="userModel.email" name="email" 
        required email #emailInput="ngModel">

    <div *ngIf="formInstance.invalid && formInstance.dirty">
      <p *ngIf="userInput.errors?.required">
        使用者名稱為必填
      </p>
      <p *ngIf="userInput.errors?.minlength">
        使用者名稱最需要{{userInput.errors?.minlength.requiredLength}}字元
      </p>
      <p *ngIf="emailInput.errors?.required">
        email 為必填
      </p>
      <p *ngIf="emailInput.errors?.email">
        email 格式錯誤
      </p>
    </div>
    <button type="submit">新增</button>
  </form>

https://ithelp.ithome.com.tw/upload/images/20210929/20140664c7w4nrXTaN.png

輕輕鬆鬆~我們就完成 Angular 的表單跟驗證,雖然他很陽春,但是只要掌握這些基礎,視情況對我們的表單做擴充,就能滿足一半以上的需求。明天我們會簡單介紹一下 Angular 的路由(routing),讓我們根據使用者巡覽的位址顯示不同的 component。


上一篇
[Day28] 用 HttpClient 從 API 取得資料
下一篇
[Day30] Angular 的 Routing
系列文
網站一條龍 - 從架站到前端33

尚未有邦友留言

立即登入留言