iT邦幫忙

2021 iThome 鐵人賽

DAY 11
1
Modern Web

關於我作夢變成工程師這檔事(Angular 篇)系列 第 11

第 11 天 範本驅動表單的動態檢核訊息|ngSubmit

  • 分享至 

  • xImage
  •  

前情提要

昨日實作其中一個英雄表單欄位「姓名」後,演示了如何使用 FormControl 表單控制項搭配範本參考變數(Template Reference)來掌握欄位的狀態。

今天我們將進一步完整整個英雄資訊表單,包括顯示欄位錯誤資訊(例如,必填欄位尚未填寫的話,會顯示相對的提示)。

最後,再來談談 Angualr 對於表單 submit 事件的處理。

加上錯誤訊息提示

我們一樣藉由必填欄位「名稱」來看看怎麼處理錯誤資訊的顯示,將 hero-information-form.component.html 調整如下:

  <div class="form-field">
    <label for="name">NAME</label>
    <input
      #tName="ngModel"
      name="name"
      ngModel
      required
      type="text"
      id="name" />
    <ng-container *ngIf="tName.invalid">
      <div *ngIf="tName.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

此時我們進入表單的時候,就會看到這個錯誤提示:

https://ithelp.ithome.com.tw/upload/images/20210926/20128395XV04RyNH1t.png

成功了!但先等一等,使用者才剛進去就要看這個提醒嗎?如果有很多欄位不就滿江紅了?

因此我們可以再加上一個表單控制項提供的屬性 touched,也就是當使用者接觸過這個欄位,但仍然沒有輸入值的時候,那麼就會出現紅字警語,這樣就不算冤枉了吧:

  <div class="form-field">
      (...)
    <ng-container *ngIf="tName.invalid && tName.touched">
      <div *ngIf="tName.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

除了這個處理方式之外,有時候我們也可能想要這樣設計:只有當使用者點擊 submit 按鈕後,才提供檢核提示文字。這時候,我們就可以使用 ngForm 提供的屬性 submitted,因此,我們要在 <form> 我們將 ngForm 賦予給範本參考變數 tForm,而 ngSubmit 事件我們等一下會解釋:

<form #tForm="ngForm" (ngSubmit)="do some thing...">
  <div class="form-field">
      (...)
    <ng-container *ngIf="tName.invalid && tForm.submitted">
      <div *ngIf="tName.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>
  <button type="submit">新增英雄</button>
 </form>

可以看到,在檢核提示資訊的顯示條件為 *ngIf="tName.invalid && tForm.submitted",因此,只有在使用者點擊 submit 按鈕「新增英雄」後,如有不合法的欄位才會顯示紅字提示。

了解錯誤資訊的動態顯示機制後,我們來為表單增加一個「重設」按鈕:

<form #tForm="ngForm" (ngSubmit)="do some thing...">
  <button type="submit">新增英雄</button>
  <button type="button" (click)="tForm.reset()">重設</button>
</form>

我們在重設按鈕監聽 click 事件,在點擊時會使用 tForm(ngForm)的方法 reset(),除了會清空資料後,也會把檢核狀態(invalid、touched...)都還原。

你不要亂送我來送

在 HTML 提交表單,預設的行為是刷新頁面。但這樣的機制在「單頁應用」(Single Page Application)的架構下卻是不合適的,因為「單頁應用」刷新頁面的話,所有的元件、狀態都會刷新,這大概不會是我們預期的結果。讓我們嘗試在表單中加入 submit 按鈕(hero-information-form.component.html):

<form>
    (...)
  <button type="submit">新增英雄</button>
</form>

點擊按鈕之後沒有發生任何事,這是因為 Angular 攔截了預設的 submit 事件,所以並不會出現我們擔心的狀況——整個 SPA 應用重建。取而代之的,當點擊 submit 按鈕時,觸發的是 ngSubmit 事件,我們可以在 <form> 標籤上來監聽這個事件,並進一步設定當 ngSumit 事件觸發時,我們要進行什麼行為,例如呼叫 doSubmit 方法。

<form (ngSubmit)="doSubmit()">
    (...)
    <button type="submit">新增英雄</button>
</form>

當然這樣不會任何事情是,因為我們沒有在 ts 檔實作 doSubmit 方法。此外,我們也沒有將這個表單的值傳給這個方法,我們先在 hero-information-form.component.ts 實作 doSubmit 方法:

doSubmit(hero: Hero): void {
    console.log('submit new hero data', hero);
}

接著我們使用範本參考變數(Template Variables)搭配 ngForm 來取得表單現在的值:

<form #tForm="ngForm" (ngSubmit)="doSubmit(tForm.value)">
    (...)
    <button type="submit">新增英雄</button>
    <button type="button" (click)="tForm.reset()">重設</button>
</form>

除外,我們可以在 submit 按鈕「新增英雄」上,針對其 disabled 進行屬性繫結,條件為 [disabled]="tForm.invalid"。因此,當表單擁有非法的欄位時,就無法點擊這個按鈕,優化使用體驗:

https://ithelp.ithome.com.tw/upload/images/20210926/20128395mJ1rHuNbgQ.png

我們先將每個表單加上必填檢核(required),在範本驅動表單預設可設定的檢核指令請參考文件,完成的表單程式碼如下:

<form
  #tForm="ngForm"
  (ngSubmit)="doSubmit(tForm)">

  <div class="form-field">
    <label for="name">NAME</label>
    <input
      #tName="ngModel"
      name="name"
      ngModel
      required
      type="text"
      id="name" />
    <ng-container *ngIf="tName.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tName.errors?.required">此欄位為必填</div>
    </ng-container>

  </div>

  <div class="form-field">
    <label for="hp">HP</label>
    <input
      #tHp="ngModel"
      name="hp"
      ngModel
      required
      type="number"
      id="hp" />
    <ng-container *ngIf="tHp.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tHp.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <div class="form-field">
    <label for="attack">ATTACK</label>
    <input
      #tAttack="ngModel"
      name="attack"
      ngModel
      required
      type="number"
      id="attack" />
    <ng-container *ngIf="tAttack.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tAttack.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <div class="form-field">
    <label for="defence">DEFENCE</label>
    <input
      #tDefence="ngModel"
      name="defence"
      ngModel
      required
      type="number"
      id="defence" />
    <ng-container *ngIf="tDefence.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tDefence.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <div class="form-field">
    <label for="weapon">WEAPON</label>
    <input
      #tWeapon="ngModel"
      name="weapon"
      ngModel
      required
      type="string"
      id="weapon" />
    <ng-container *ngIf="tWeapon.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tWeapon.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <div class="form-field">
    <label for="skill">SKILL</label>
    <input
      #tSkill="ngModel"
      name="skill"
      ngModel
      required
      type="string"
      id="skill" />
    <ng-container *ngIf="tSkill.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tSkill.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <div class="form-field">
    <label for="description">
      DESCRIPTION
    </label>
    <textarea
      ngModel
      required
      #tDescription="ngModel"
      name="description"
      id="description"
      cols="30"
      rows="10">
    </textarea>
    <ng-container *ngIf="tDescription.invalid && tForm.submitted">
      <div class="error-message" *ngIf="tDescription.errors?.required">此欄位為必填</div>
    </ng-container>
  </div>

  <button
    type="submit"
    [disabled]="tForm.invalid">
    新增英雄
  </button>

  <button
    type="button"
    (click)="tForm.reset()">
    重設表單
  </button>

</form>

完整程式碼已推上 Github


上一篇
第 10 天 別說呂布了,你聽過青銅五小強嗎 |Template-driven-form、ngModel、Template variables
下一篇
第 12 天 範本變數加範本輸入變數|template variables、template input variables
系列文
關於我作夢變成工程師這檔事(Angular 篇)14
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言