第 11 天 范本驱动表单的动态检核讯息|ngSubmit

前情提要

昨日实作其中一个英雄表单栏位「姓名」後,演示了如何使用 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


<<:  Day11-Kubernetes 那些事 - Ingress 篇(三)

>>:  [C 语言笔记--Day15] 如何清空终端机

[FGL] OPEN WINDOW WITH 画面档

前几个篇章中,若使用到客户端 (如GDC/GBC) 呈现画面时,Hello World 都只能出现...

每个人都该学的30个Python技巧|技巧 24:超便利的内建函式—max()、min()、sum()(字幕、衬乐、练习)

昨天才发现原来我之前的影片声音都很奇怪(怎麽没有人跟我说啦 (ఠ్ఠ ˓̭ ఠ్ఠ)),所以今天调了...

Composition vs 继承( Day13 )

React 具有强大的 composition 模型,我们建议你在 component 之间使用 ...

[Day4] API开发规格书

看完永丰的API规格书,开始盘点所需之参数。 由规格书可知,呼叫API所需要的参数有Version、...

[DAY 29] Edge Computing v.s PC Computing

前言 我们知道了如何在个人电脑上执行训练/使用一个 Deep Learning Model ,更进一...