Angular 4.x中表单Reactive Forms详解


Posted in Javascript onApril 25, 2017

Angular 4.x 中有两种表单:

  • Template-Driven Forms - 模板驱动式表单 (类似于 Angular 1.x 中的表单 )
  • Reactive Forms (Model-Driven Forms) - 响应式表单

Template-Driven Forms (模板驱动表单) ,我们之前的文章已经介绍过了,了解详细信息,请查看 - Angular 4.x Template-Driven Forms 。

Contents

  • ngModule and reactive forms
  • FormControl and FormGroup
  • Implementing our FormGroup model
  • Binding our FormGroup model
  • Reactive submit
  • Reactive error validation
  • Simplifying with FormBuilder

Form base and interface

Form base

<form novalidate>
 <label>
 <span>Full name</span>
 <input
 type="text"
 name="name"
 placeholder="Your full name">
 </label>
 <div>
 <label>
 <span>Email address</span>
 <input
 type="email"
 name="email"
 placeholder="Your email address">
 </label>
 <label>
 <span>Confirm address</span>
 <input
 type="email"
 name="confirm"
 placeholder="Confirm your email address">
 </label>
 </div>
 <button type="submit">Sign up</button>
</form>

接下来我们要实现的功能如下:

  • 绑定 name、email、confirm 输入框的值
  • 为所有输入框添加表单验证功能
  • 显示验证异常信息
  • 表单验证失败时,不允许进行表单提交
  • 表单提交功能

User interface

// signup.interface.ts
export interface User {
 name: string;
 account: {
 email: string;
 confirm: string;
 }
}

ngModule and reactive forms

在我们继续深入介绍 reactive forms 表单前,我们必须在 @NgModule 中导入 @angular/forms 库中的 ReactiveFormsModule:

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
 imports: [
 ...,
 ReactiveFormsModule
 ],
 declarations: [...],
 bootstrap: [...]
})
export class AppModule {}

友情提示:若使用 reactive forms,则导入 ReactiveFormsModule;若使用 template-driven 表单,则导入 FormsModule。

Reactive approach

我们将基于上面的定义的基础表单,创建 SignupFormComponent :

signup-form.component.ts

import { Component } from '@angular/core';

@Component({
 selector: 'signup-form',
 template: `
 <form novalidate>...</form>
 `
})
export class SignupFormComponent {
 constructor() {}
}

这是一个基础的组件,在我们实现上述功能前,我们需要先介绍 FormControl、FormGroup、FormBuilder 的概念和使用。

FormControl and FormGroup

我们先来介绍一下 FormControl 和 FormGroup 的概念:

1、FormControl - 它是一个为单个表单控件提供支持的类,可用于跟踪控件的值和验证状态,此外还提供了一系列公共API。
使用示例:

ngOnInit() {
 this.myControl = new FormControl('Semlinker');
}

2、FormGroup - 包含是一组 FormControl 实例,可用于跟踪 FormControl 组的值和验证状态,此外也提供了一系列公共API。

使用示例:

ngOnInit() {
 this.myGroup = new FormGroup({
 name: new FormControl('Semlinker'),
 location: new FormControl('China, CN')
 });
}

现在我们已经创建了 FormControl 和 FormGroup 实例,接下来我们来看一下如何使用:

<form novalidate [formGroup]="myGroup">
 Name: <input type="text" formControlName="name">
 Location: <input type="text" formControlName="location">
</form>

注意事项:Template-Driven Forms 中介绍的 ngModel 和 name="" 属性,已经被移除了。这是一件好事,让我们的模板更简洁。

上面示例中,我们必须使用 [formGroup] 绑定我们创建的 myGroup 对象,除此之外还要使用 formControlName 指令,绑定我们创建的 FormControl 控件。

此时的表单结构如下:

FormGroup -> 'myGroup'
 FormControl -> 'name'
 FormControl -> 'location'

Implementing our FormGroup model

signup.interface.ts

export interface User {
 name: string;
 account: {
 email: string;
 confirm: string;
 }
}

与之对应的表单结构如下:

FormGroup -> 'user'
 FormControl -> 'name'
 FormGroup -> 'account'
 FormControl -> 'email'
 FormControl -> 'confirm'

是的,我们可以创建嵌套的 FormGroup 集合!让我们更新一下组件 (不包含初始数据):

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';

@Component({...})
export class SignupFormComponent implements OnInit {
 user: FormGroup;
 ngOnInit() {
 this.user = new FormGroup({
 name: new FormControl(''),
 account: new FormGroup({
 email: new FormControl(''),
 confirm: new FormControl('')
 })
 });
 }
}

如果我们想要设置初始数据,我们可以按照上述示例进行设置。通常情况下,我们通过服务端提供的 API 接口来获取表单的初始信息。

Binding our FormGroup model

现在我们已经实例化了 FormGroup 模型,是时候绑定到对应的 DOM 元素上了。具体示例如下:

<form novalidate [formGroup]="user">
 <label>
 <span>Full name</span>
 <input
 type="text"
 placeholder="Your full name"
 formControlName="name">
 </label>
 <div formGroupName="account">
 <label>
 <span>Email address</span>
 <input
 type="email"
 placeholder="Your email address"
 formControlName="email">
 </label>
 <label>
 <span>Confirm address</span>
 <input
 type="email"
 placeholder="Confirm your email address"
 formControlName="confirm">
 </label>
 </div>
 <button type="submit">Sign up</button>
</form>

现在 FormGroup 与 FormControl 对象与 DOM 结构的关联信息如下:

// JavaScript APIs
FormGroup -> 'user'
 FormControl -> 'name'
 FormGroup -> 'account'
 FormControl -> 'email'
 FormControl -> 'confirm'

// DOM bindings
formGroup -> 'user'
 formControlName -> 'name'
 formGroupName -> 'account'
 formControlName -> 'email'
 formControlName -> 'confirm'

当使用模板驱动的表单时,为了获取 f.value 表单的值,我们需要先执行 #f="ngForm" 的操作。而对于使用响应式的表单,我们可以通过以下方式,方便的获取表单的值:

{{ user.value | json }} // { name: '', account: { email: '', confirm: '' }}

Reactive submit

跟模板驱动的表单一样,我们可以通过 ngSubmit 输出属性,处理表单的提交逻辑:

<form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
 ...
</form>

需要注意的是:我们使用 user 对象作为 onSubmit() 方法的参数,这使得我们可以获取表单对象的相关信息,具体处理逻辑如下:

export class SignupFormComponent {
 user: FormGroup;
 onSubmit({ value, valid }: { value: User, valid: boolean }) {
 console.log(value, valid);
 }
}

上面代码中,我们使用 Object destructuring (对象解构) 的方式,从user 对象中获取 value 和 valid 属性的值。其中 value 的值,就是 user.value 的值。在实际应用中,我们是不需要传递 user 参数的:

export class SignupFormComponent {
 user: FormGroup;
 onSubmit() {
 console.log(this.user.value, this.user.valid);
 }
}

表单的数据绑定方式和提交逻辑已经介绍完了,是该介绍表单实际应用中,一个重要的环节 — 表单验证。

Reactive error validation

接下来我们来为表单添加验证规则,首先我们需要从 @angular/forms 中导入 Validators。具体使用示例如下:

ngOnInit() {
 this.user = new FormGroup({
 name: new FormControl('', [Validators.required, Validators.minLength(2)]),
 account: new FormGroup({
 email: new FormControl('', Validators.required),
 confirm: new FormControl('', Validators.required)
 })
 });
}

通过以上示例,我们可以看出,如果表单控制包含多种验证规则,可以使用数组声明多种验证规则。若只包含一种验证规则,直接声明就好。通过这种方式,我们就不需要在模板的输入控件中添加 required 属性。接下来我们来添加表单验证失败时,不允许进行表单提交功能:

<form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
 ...
 <button type="submit" [disabled]="user.invalid">Sign up</button>
</form>

那么问题来了,我们要如何获取表单控件的验证信息?我们可以使用模板驱动表单中介绍的方式,具体如下:

<form novalidate [formGroup]="user">
 {{ user.controls.name?.errors | json }}
</form>

友情提示: ?.prop 称为安全导航操作符,用于告诉 Angular prop 的值可能不存在。

此外我们也可以使用 FormGroup 对象提供的 API,来获取表单控件验证的错误信息:

<form novalidate [formGroup]="user">
 {{ user.get('name').errors | json }}
</form>

现在我们来看一下完整的代码:

import { Component, OnInit } from '@angular/core';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { User } from './signup.interface';

@Component({
 selector: 'signup-form',
 template: `
 <form novalidate (ngSubmit)="onSubmit(user)" [formGroup]="user">
  <label>
  <span>Full name</span>
  <input type="text" placeholder="Your full name" formControlName="name">
  </label>
  <div class="error" *ngIf="user.get('name').hasError('required') && 
   user.get('name').touched">
  Name is required
  </div>
  <div class="error" *ngIf="user.get('name').hasError('minlength') && 
   user.get('name').touched">
  Minimum of 2 characters
  </div>
  <div formGroupName="account">
  <label>
   <span>Email address</span>
   <input type="email" placeholder="Your email address" formControlName="email">
  </label>
  <div
   class="error"
   *ngIf="user.get('account').get('email').hasError('required') && 
    user.get('account').get('email').touched">
   Email is required
  </div>
  <label>
   <span>Confirm address</span>
   <input type="email" placeholder="Confirm your email address" 
    formControlName="confirm">
  </label>
  <div
   class="error"
   *ngIf="user.get('account').get('confirm').hasError('required') && 
    user.get('account').get('confirm').touched">
   Confirming email is required
  </div>
  </div>
  <button type="submit" [disabled]="user.invalid">Sign up</button>
 </form>
 `
})
export class SignupFormComponent implements OnInit {
 user: FormGroup;
 constructor() {}
 ngOnInit() {
 this.user = new FormGroup({
  name: new FormControl('', [Validators.required, Validators.minLength(2)]),
  account: new FormGroup({
  email: new FormControl('', Validators.required),
  confirm: new FormControl('', Validators.required)
  })
 });
 }
 onSubmit({ value, valid }: { value: User, valid: boolean }) {
 console.log(value, valid);
 }
}

功能是实现了,但创建 FormGroup 对象的方式有点繁琐,Angular 团队也意识到这点,因此为我们提供 FormBuilder ,来简化上面的操作。

Simplifying with FormBuilder

首先我们需要从 @angular/forms 中导入 FormBuilder:

import { FormBuilder, FormGroup, Validators } from '@angular/forms';

export class SignupFormComponent implements OnInit {
 user: FormGroup;
 constructor(private fb: FormBuilder) {}
 ...
}

然后我们使用 FormBuilder 对象提供的 group() 方法,来创建 FormGroup 和 FormControl 对象:

调整前的代码 (未使用FormBuilder):

ngOnInit() {
 this.user = new FormGroup({
 name: new FormControl('', [Validators.required, Validators.minLength(2)]),
 account: new FormGroup({
  email: new FormControl('', Validators.required),
  confirm: new FormControl('', Validators.required)
 })
 });
}

调整后的代码 (使用FormBuilder):

ngOnInit() {
 this.user = this.fb.group({
 name: ['', [Validators.required, Validators.minLength(2)]],
 account: this.fb.group({
  email: ['', Validators.required],
  confirm: ['', Validators.required]
 })
 });
}

对比一下调整前和调整后的代码,是不是感觉一下子方便了许多。此时更新完后完整的代码如下:

@Component({...})
export class SignupFormComponent implements OnInit {
 user: FormGroup;
 constructor(private fb: FormBuilder) {}
 ngOnInit() {
 this.user = this.fb.group({
  name: ['', [Validators.required, Validators.minLength(2)]],
  account: this.fb.group({
  email: ['', Validators.required],
  confirm: ['', Validators.required]
  })
 });
 }
 onSubmit({ value, valid }: { value: User, valid: boolean }) {
 console.log(value, valid);
 }
}

我有话说

Template-Driven Forms vs Reactive Forms

Template-Driven Forms (模板驱动表单) 的特点

  • 使用方便
  • 适用于简单的场景
  • 通过 [(ngModel)] 实现数据双向绑定
  • 最小化组件类的代码
  • 不易于单元测试

Reactive Forms (响应式表单) 的特点

  • 比较灵活
  • 适用于复杂的场景
  • 简化了HTML模板的代码,把验证逻辑抽离到组件类中
  • 方便的跟踪表单控件值的变化
  • 易于单元测试

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
JavaScript 字符串与数组转换函数[不用split与join]
Dec 13 Javascript
基于jquery的时间段实现代码
Aug 02 Javascript
Jquery搜索父元素操作方法
Feb 10 Javascript
JavaScript中的acos()方法使用详解
Jun 14 Javascript
详解JavaScript对象序列化
Jan 19 Javascript
基于jQuery实现select下拉选择可输入附源码下载
Feb 03 Javascript
jquery弹出遮掩层效果【附实例代码】
Apr 28 Javascript
javascript笔记之匿名函数和闭包
Feb 06 Javascript
javascript防篡改对象实例详解
Apr 10 Javascript
基于js Canvas实现二次贝塞尔曲线
Dec 25 Javascript
聊聊鉴权那些事(推荐)
Aug 22 Javascript
vue单元格多列合并的实现
Nov 26 Vue.js
Angular 4.x 动态创建表单实例
Apr 25 #Javascript
AngularJS动态菜单操作指令
Apr 25 #Javascript
Angular.js 4.x中表单Template-Driven Forms详解
Apr 25 #Javascript
详解JS中的attribute属性
Apr 25 #Javascript
node.js中debug模块的简单介绍与使用
Apr 25 #Javascript
Node.js利用debug模块打印出调试日志的方法
Apr 25 #Javascript
JS实现禁止高频率连续点击的方法【基于ES6语法】
Apr 25 #Javascript
You might like
让你同时上传 1000 个文件 (二)
2006/10/09 PHP
php 文章采集正则代码
2009/12/28 PHP
30 个很棒的PHP开源CMS内容管理系统小结
2011/10/14 PHP
Ajax实时验证用户名/邮箱等是否已经存在的代码打包
2011/12/01 PHP
php多种形式发送邮件(mail qmail邮件系统 phpmailer类)
2014/01/22 PHP
php通过array_unshift函数添加多个变量到数组前端的方法
2015/03/18 PHP
E3 tree 1.6在Firefox下显示问题的修复方法
2013/01/30 Javascript
JavaScript实现的图像模糊算法代码分享
2014/04/22 Javascript
JavaScript中this详解
2015/09/01 Javascript
每天一篇javascript学习小结(属性定义方法)
2015/11/19 Javascript
javascript中call,apply,callee,caller用法实例分析
2019/07/24 Javascript
用Golang运行JavaScript的实现示例
2019/11/25 Javascript
微信小程序 scroll-view的使用案例代码详解
2020/06/11 Javascript
[01:06:32]DOTA2上海特级锦标赛D组资格赛#1 EG VS VP第一局
2016/02/28 DOTA
[07:54]DOTA2-DPC中国联赛 正赛 iG vs VG 选手采访
2021/03/11 DOTA
python实现kNN算法
2017/12/20 Python
python实现淘宝秒杀脚本
2020/06/23 Python
Python实现多线程/多进程的TCP服务器
2019/09/03 Python
python输出第n个默尼森数的实现示例
2020/03/08 Python
python3.7添加dlib模块的方法
2020/07/01 Python
python 如何引入协程和原理分析
2020/11/30 Python
html5启动原生APP总结
2020/07/03 HTML / CSS
德国婴儿推车和儿童安全座椅商店:BABYSHOP
2016/09/01 全球购物
以实惠的价格提供高品质的时尚:Newchic
2018/01/18 全球购物
Aeropostale官网:美国著名校园品牌及青少年服饰品牌
2019/03/21 全球购物
Hanky Panky官方网站:内衣和睡衣
2019/07/25 全球购物
土木工程毕业生自荐信
2013/09/21 职场文书
低碳生活的宣传标语
2014/06/23 职场文书
领导班子整改方案和个人整改措施
2014/10/25 职场文书
2014年作风建设工作总结
2014/10/29 职场文书
2015年南京大屠杀纪念日活动总结
2015/03/24 职场文书
2015年公务员试用期工作总结
2015/05/28 职场文书
婚庆主持词大全
2015/06/30 职场文书
《失物招领》教学反思
2016/02/20 职场文书
深度好文:50条没人告诉你的人生经验,句句精辟
2019/08/22 职场文书
导游词之重庆渣滓洞
2020/01/08 职场文书