在 Angular知识学习(一)中有讲述到表单的知识,不过那是最基础的演示,在之后的学习中又了解到模板驱动表单,所以考虑对之前的表单案例进行重构,完善表单功能,让案例更接近应用。
根据官网模板驱动表单的知识内容,我们重新构建人员登记表单,主要分为以下步骤:
- 创建 Uuser 模型类
- 创建控制此表单的组件
- 创建具有初始表单布局的模板。
- 使用
ngModel
双向数据绑定语法把数据属性绑定到每个表单输入控件。 - 往每个表单输入控件上添加
name
属性 (attribute)。 - 添加自定义 CSS 来提供视觉反馈。
- 显示和隐藏有效性验证的错误信息。
- 使用 ngSubmit 处理表单提交。
- 禁用此表单的提交按钮,直到表单变为有效。
创建User模型类
使用 Angular CLI 命令 ng g class
生成一个名叫 Uuer
的新类:
ng g class model/uuer
内容如下:
export class Uuser {
constructor(
public name: string,
public sex: string,
public city: string,
public hobbies: any[],
public remark: string
) {
}
}
该类主要包含五个属性,分别是姓名、性别、城市、爱好和备注。其中爱好有多个,所以用数组来表示。
创建表单组件
使用 Angular CLI 命令 ng g component
生成一个名叫 UserForm
的新组件:
ng g component components/userForm
因为模板驱动的表单位于它们自己的模块,所以在使用表单之前,需要将 FormsModule
添加到应用模块的 imports
数组中。对 app.module.ts
进行修改:
import { FormsModule } from '@angular/forms';
imports: [
BrowserModule,
FormsModule
]
有两处更改
- 导入
FormsModule
。 - 把
FormsModule
添加到ngModule
装饰器的imports
列表中,这样应用就能访问模板驱动表单的所有特性,包括ngModel
。
关于表单内容的分析,官方文档写的非常详细,这里我只针对本案例中的难点进行分析,其他细节部分可以阅读官方文档。
user-form.component.html
内容如下:
<h2>人员登记系统</h2>
<div class="container">
<div [hidden]="submitted">
<form (ngSubmit)="onSubmit()" #form="ngForm">
<div class="form-group">
<label for="name">姓 名</label>
<input class="form-control" id="name" type="text" required name="name" [(ngModel)]="user.name" #name="ngModel">
<span [hidden]="name.valid || name.pristine" class="alert alert-danger">Name is required</span>
</div>
<div class="form-group">
<label>性 别 </label>
<div class="radio-inline">
<input type="radio" value="男" name="sex" id="man" [(ngModel)]="user.sex" > <label for="man">男</label>
</div>
<div class="radio-inline">
<input type="radio" value="女" name="sex" id="woman" [(ngModel)]="user.sex" > <label for="woman">女</label>
</div>
</div>
<div class="form-group">
<label for="city">城 市</label>
<select class="form-control" id="city" required name="city" [(ngModel)]="user.city" #city="ngModel">
<option *ngFor="let ct of cities" [value]="ct">{{ct}}</option>
</select>
<span [hidden]="city.valid || city.pristine" class="alert alert-danger">City is required</span>
</div>
<div class="form-group">
<label for="hobby">爱 好</label>
<span *ngFor="let item of user.hobbies;let key=index" class="checkbox-inline">
<input type="checkbox" [id]="'check'+key" [(ngModel)]="item.status" [name]="'check'+key"><label [for]="'check'+key">{{item.title}}</label>
</span>
</div>
<div class="form-group">
<label for="remark">备 注</label>
<textarea class="form-control" id="remark" type="text" required name="remark" [(ngModel)]="user.remark" #remark="ngModel"></textarea>
<span [hidden]="remark.valid || remark.pristine" class="alert alert-danger">remark is required</span>
</div>
<button type="submit" class="btn btn-success" [disabled]="!form.valid">Submit</button>
<button type="button" class="btn btn-default" (click)="newUser();form.reset()">New User</button>
</form>
</div>
<div [hidden]="!submitted">
<h2>You submitted the following:</h2>
<div class="row">
<div class="col-xs-3">姓 名</div>
<div class="col-xs-9">{{ user.name }}</div>
</div>
<div class="row">
<div class="col-xs-3">性 别</div>
<div class="col-xs-9">{{ user.sex }}</div>
</div>
<div class="row">
<div class="col-xs-3">城 市</div>
<div class="col-xs-9">{{ user.city }}</div>
</div>
<div class="row">
<div class="col-xs-3">爱 好</div>
<div class="col-xs-9">
<span *ngFor="let item of user.hobbies">
<span *ngIf="item.status == 1">{{item.title}}</span>
</span>
</div>
</div>
<div class="row">
<div class="col-xs-3">备 注</div>
<div class="col-xs-9">{{ user.remark }}</div>
</div>
<br>
<button class="btn btn-primary" (click)="submitted=false">Edit</button>
</div>
</div>
同官方文档中案例相比,本文的案例增加了单选框和多选框的应用,尤其是多选框,考虑到数据的双向绑定,所以必须对 user.hobbies
属性进行初始化,通过勾选前台按钮,来改变 user.hobbies
的状态值,从而达到信息记录的目的。
user-form.component.ts
内容如下:
import { Component, OnInit } from '@angular/core';
import { Uuser } from '../../model/uuser';
@Component({
selector: 'app-user-form',
templateUrl: './user-form.component.html',
styleUrls: ['./user-form.component.css']
})
export class UserFormComponent implements OnInit {
submitted = false;
cities = ['北京', '上海', '广州 ', '深圳', '杭州', '武汉', '成都'];
hobbies = ['唱歌', '跳舞', '跑步', '健身', '游泳'];
user = new Uuser('', '男', this.cities[1], [], '');
constructor() { }
ngOnInit(): void {
this.setHobbies();
}
//每个User对象都初始化hobby属性,只是status值默认为0,通过前台勾选来修改status
setHobbies() {
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < this.hobbies.length; i++) {
this.user.hobbies.push(
{
title: this.hobbies[i],
status: 0
}
);
}
}
onSubmit() {
this.submitted = true;
}
newUser() {
this.user = new Uuser('', '', '', [], '');
this.setHobbies();
}
}
为了增加前台表单的观赏性,对表单的 CSS 样式做了一些修改。
首先是 styles.css
,引入 Bootstrap 样式,对表单整体框架显示进行优化。
@import url('https://unpkg.com/bootstrap@3.3.7/dist/css/bootstrap.min.css');
其次是 user-form.component.css
h2{
text-align: center;
}
.ng-valid[required], .ng-valid.required {
border-left: 5px solid #42A948; /* green */
}
.ng-invalid:not(form) {
border-left: 5px solid #a94442; /* red */
}
可以在输入框的左侧添加带颜色的竖条,当在必填字段输入有效内容时,输入框左侧会变为绿色,否则视为无效,变为红色。
页面测试
对上述的代码进行验证,首先完整走一遍逻辑过程。
我们来梳理一下流程,首先是维护一个人的基本信息,点击 Submit 按钮提交表单,即跳转到人员信息查看页面,再点击 Exit 按钮退回到人员维护页面,点击 New User 按钮,清空页面内容,重新录入人员信息,再次提交表单可正常跳转到人员信息查看页面。
测试过程中可以发现,当必填项不填写内容,Submit 按钮始终是灰色的,即无法点击提交,此处是对整个表单进行有效验证,在实际应用中也是很有必要的。
关于性别单选框有一点需要注意:平时我们设置单选框的默认值,加上 checked 即可,但是由于在当前案例中使用了双向数据绑定,所以该属性不起作用,必须给 user.sex 设置默认值,从而实现单选框的默认选定。
关于爱好多选框,从数据双向绑定的角度来看,是无法向 user.hobbies 中增加数据,多选框的勾选只是状态值的改变,所以在 user-form.component.ts
文件中会增加一个 setHobbies
方法。
总结
本文对于 Angular 表单的使用进行了优化,利用框架特性来支持数据修改、验证和更多操作:
- Angular HTML 表单模板。
- 带有
@Component
装饰器的表单组件类。 - 通过绑定到
NgForm.ngSubmit
事件属性来处理表单提交。 - 模板引用变量,例如
#form
和#name
。 [(ngModel)]
语法用来实现双向数据绑定。name
属性的用途是有效性验证和对表单元素的变更进行追踪。- 指向 input 控件的引用变量上的
valid
属性,可用于检查控件是否有效、是否显示/隐藏错误信息。 - 通过绑定到
NgForm
的有效性状态,控制 Submit 按钮的禁用状态。 - 定制 CSS 类来给用户提供无效控件的视觉反馈。
参考文献
本文作者为hresh,转载请注明。