昨日实作其中一个英雄表单栏位「姓名」後,演示了如何使用 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>
此时我们进入表单的时候,就会看到这个错误提示:
成功了!但先等一等,使用者才刚进去就要看这个提醒吗?如果有很多栏位不就满江红了?
因此我们可以再加上一个表单控制项提供的属性 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"
。因此,当表单拥有非法的栏位时,就无法点击这个按钮,优化使用体验:
我们先将每个表单加上必填检核(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 篇(三)
前几个篇章中,若使用到客户端 (如GDC/GBC) 呈现画面时,Hello World 都只能出现...
昨天才发现原来我之前的影片声音都很奇怪(怎麽没有人跟我说啦 (ఠ్ఠ ˓̭ ఠ్ఠ)),所以今天调了...
React 具有强大的 composition 模型,我们建议你在 component 之间使用 ...
看完永丰的API规格书,开始盘点所需之参数。 由规格书可知,呼叫API所需要的参数有Version、...
前言 我们知道了如何在个人电脑上执行训练/使用一个 Deep Learning Model ,更进一...