在 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 类来给用户提供无效控件的视觉反馈。