Angular之模板驱动表单

hresh 503 0

Angular之模板驱动表单

Angular知识学习(一)中有讲述到表单的知识,不过那是最基础的演示,在之后的学习中又了解到模板驱动表单,所以考虑对之前的表单案例进行重构,完善表单功能,让案例更接近应用。

根据官网模板驱动表单的知识内容,我们重新构建人员登记表单,主要分为以下步骤:

  1. 创建 Uuser 模型类
  2. 创建控制此表单的组件
  3. 创建具有初始表单布局的模板。
  4. 使用 ngModel 双向数据绑定语法把数据属性绑定到每个表单输入控件。
  5. 往每个表单输入控件上添加 name 属性 (attribute)。
  6. 添加自定义 CSS 来提供视觉反馈。
  7. 显示和隐藏有效性验证的错误信息。
  8. 使用 ngSubmit 处理表单提交。
  9. 禁用此表单的提交按钮,直到表单变为有效。

创建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
  ]

有两处更改

  1. 导入 FormsModule
  2. 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 */
}

可以在输入框的左侧添加带颜色的竖条,当在必填字段输入有效内容时,输入框左侧会变为绿色,否则视为无效,变为红色。

页面测试

对上述的代码进行验证,首先完整走一遍逻辑过程。

Angular之模板驱动表单

我们来梳理一下流程,首先是维护一个人的基本信息,点击 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 类来给用户提供无效控件的视觉反馈。

参考文献

官方文档

发表评论 取消回复
表情 图片 链接 代码

分享