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 无符号右移赋值操作
Apr 17 Javascript
JavaScript 函数replace深入了解
Mar 14 Javascript
jQuery阻止事件冒泡具体实现
Oct 11 Javascript
js onclick事件传参讲解
Nov 06 Javascript
jquery实现文本框数量加减功能的例子分享
May 10 Javascript
JavaScript Serializer序列化时间处理示例
Jul 31 Javascript
浅谈jQuery中对象遍历.eq().first().last().slice()方法
Nov 26 Javascript
基于Flowplayer打造一款免费的WEB视频播放器附源码
Sep 06 Javascript
JavaScript实现输入框(密码框)出现提示语
Jan 12 Javascript
JS简单测试循环运行时间的方法
Sep 04 Javascript
详解小程序input框失焦事件在提交事件前的处理
May 05 Javascript
JS中多层次排序算法的实现代码
Jan 06 Javascript
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
实现dedecms全站URL静态化改造的代码
2007/03/29 PHP
php一些公用函数的集合
2008/03/27 PHP
thinkphp文件引用与分支结构用法实例
2014/11/26 PHP
thinkphp框架实现删除和批量删除
2016/06/29 PHP
JavaScript 异步调用框架 (Part 2 - 用例设计)
2009/08/03 Javascript
jQuery获取CSS样式中的颜色值的问题,不同浏览器格式不同的解决办法
2013/05/13 Javascript
JS方法调用括号的问题探讨
2014/01/24 Javascript
jquery处理页面弹出层查询数据等待操作实例
2015/03/25 Javascript
javascript中关于&amp;&amp; 和 || 表达式的小技巧分享
2015/04/10 Javascript
JavaScript实现显示函数调用堆栈的方法
2016/04/21 Javascript
javascript经典特效分享 手风琴、轮播图、图片滑动
2016/09/14 Javascript
JavaScript reduce和reduceRight详解
2016/10/24 Javascript
微信小程序 设置启动页面的两种方法
2017/03/09 Javascript
vue.js数据绑定的方法(单向、双向和一次性绑定)
2017/07/13 Javascript
Java设计中的Builder模式的介绍
2018/03/22 Javascript
vue弹窗消息组件的使用方法
2020/09/24 Javascript
微信小程序新手教程之启动页的重要性
2019/03/03 Javascript
python uuid模块使用实例
2015/04/08 Python
Python获取暗黑破坏神3战网前1000命位玩家的英雄技能统计
2016/07/04 Python
Python图片裁剪实例代码(如头像裁剪)
2017/06/21 Python
python代理工具mitmproxy使用指南
2019/07/04 Python
Python求两点之间的直线距离(2种实现方法)
2019/07/07 Python
python集合常见运算案例解析
2019/10/17 Python
Python Django搭建网站流程图解
2020/06/13 Python
学python最电脑配置有要求么
2020/07/05 Python
CSS3实现的闪烁跳跃进度条示例(附源码)
2013/08/19 HTML / CSS
微软台湾官方网站:Microsoft台湾
2018/08/15 全球购物
微软加拿大官方网站:Microsoft Canada
2019/04/28 全球购物
应用电子技术专业个人求职信
2013/09/21 职场文书
新学期开学寄语
2014/01/18 职场文书
《玩具柜台前的孩子》教学反思
2014/02/13 职场文书
党的群众路线教育实践活动学习心得体会
2014/03/03 职场文书
我的梦想演讲稿
2014/04/30 职场文书
表彰大会策划方案
2014/05/13 职场文书
本科毕业生求职信
2014/06/15 职场文书
诚实守信道德模范事迹材料
2014/08/15 职场文书