详解Angular2响应式表单


Posted in Javascript onJune 14, 2017

本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面。

文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件。

响应式表单是一项响应式风格的ng2技术,本文将解释响应式表单并用来创建一个英雄详情编辑器。

包含内容:

  1. 响应式表单介绍
  2. 开始搭建
  3. 创建数据模型
  4. 创建响应式的表单组件
  5. 创建组建的模板文件
  6. 引入ReactiveFormsModule
  7. 显示HeroDetailComponent
  8. 添加一个FormGroup
  9. 看看表单模型
  10. 介绍FormBuilder
  11. 验证的需求
  12. 放置FormGroup
  13. 检查FormControl属性
  14. 使用setValue以及patchValue设置表单模型数据
  15. 使用FotmArray提供FormGroup的数组
  16. 观察控件的更改

 响应式表单介绍

angular提供了两种表单搭建技术: 响应式表单和模板驱动式表单。都依赖于@angular/forms库,并共享了一些通用的表单控件集。
但是他们在原理、代码风格以及技术上存在区别。他们甚至有自己的模块:ReactiveFormsModule以及FormsModule。

响应式表单(ReactiveFormsModule):

anguar的响应式表单简化了管理数据时响应式风格的编码实现,使用了在无视图数据模型(从服务器获取)以及以视图为导向的模型用于保持屏幕上HTML控件显示的值与状态。响应式表单提供了响应式模式测试以及验证上的便利。

使用响应式表单,你将在组件类中创建一个anular的表单控件树对象,并在组件模板中使用提供的技术绑定到原生表单控件标签中。

你直接在组件类中创建并操作控件对象。因为组件类能直接访问到数据模型以及表单控件结构,你可以将数据模型值推送到表单控件以及将用户的更改响应到后边来。组件可以观察表单控件状态的更改并响应这些更改。

直接使用表单控件对象工作的一个好处是值以及验证的更新总能够同步完成并受你控制。你不会遇到有时候因为模板驱动表单造成的时间问题,并且响应式表单更易测试。

为了保持响应的一致性,组件会保存不一致的数据模型,将其视为纯粹的原始值。不会直接更新数据模型,组件会提取用户的更改并转发到外面的组件或服务中,(可能是用来保存他们的)并返回一个新数据模型到组件,用于响应模型状态的更新。

使用响应式表单指令不需要你依赖于全部响应式原理,但是这确实能促进响应式编程方法如果你选择了要使用这个方法的话。

模板驱动式表单(FormsModule):

模板驱动式的表单使用了完全不同的方式。

你在组件模板中放置HTML表单控件(input这些)并使用比如ngModel这些指令绑定到数据模型属性。

你不需要创建angular表单控件对象,因为angular会根据你的数据绑定信息自动帮你创建出来。你不是推送或者拉取数据值。angular在ngModel中帮你处理了。angular会更新那些被改变的数据值。

出于这个原因,ngModel不再是ReactiveFormsModule的一部分了。

这意味着可以在组件类中写更少的代码,不过模板驱动表单是异步工作的,这可能会在某些情况下复杂化开发。

同步vs异步

响应式表单是同步的。模板驱动表单是异步的,这是其区别的根源。

在响应式表单中,你在代码中创建一个完整的表单控件树。你可以从子表单或父表单中立即更新或取回一个值,因为所有的控件都可访问到。

模板驱动表单将他们的表单控件的创建委托给了指令。为了避免“检查后又更改”的错误,这些指令使用了不止一个循环来建立整个控件树。这意味着你必须在操作任何组件类中的空间表单时等那么一小会儿。

比如说,如果你使用@ViewChild(NgForm)查询注入到表单控件中并在ngAfterViewInit这个生命周期钩子中检查它,你将发现它没有子元素。你必须等一会,使用setTimeout来等待,然后你才能从这个空间中去除值并验证它或者将它设置为新的值。

模板驱动表单的异步性同时复杂化了单元测试。你必须使用async()或者fakeAsync()来包装你的测试块来避免找不到表单的值。而如果使用的是响应式表单,一切都如你所愿的存在着。

哪一个方式更好?

没有哪种是更好的。他们是两种不同的搭建方式,各自拥有长处和短处。使用最适合你的方式才是对的。在一个应用中你可能两种方式都要使用到。

本文仅仅会描述响应式的范例与精华所在。对于模板驱动式表单,可以前往表单介绍页。

接下来你将写出你自己的项目来演示响应式表单。然后你将学会关于angular表单类以及如何在响应式表单中使用它。

对上文的总结就是,相比ng1中数据的双向绑定,ng2保留了这个双向绑定能力(底层其实优化了很多),原先的ng-model指令升级成了ngModel,使用的功能保持不变。

同时尽管ng2版本的数据双向绑定得到了很大的优化,仍改变不了其数据异步绑定的方式,因为ng2不能确定数据何时绑定,我们也不能确定很多网络请求得到的数据到来的时间。

在ng1中其实这个机制会有一些尴尬的场景,至少笔者在一些情况下不得不在一些业务场景下使用setTimeout来保证数据已经成功绑定进入scope的watch循环,但这个异步绑定数据又是不可避免的,除非我们自己来适应实际项目改写angular代码了。

所以ng2就提供了让我们配合具体项目场景改写ngModel的能力,也就是原文介绍的响应式表单。

其跟ngModel的关系就是,ngModel是响应式表单的官方实现,其在我们绑定数据时自动为我们实现响应式表单中用到的几个机制,如果我们需要数据严格的实时同步绑定,就不必使用ngModel,可以亲自来编写响应式的表单,步骤覆盖了组件模板到数据模型类整条龙,而这么多事情在合适的场景下使用ngModel已经可以实现了,这两种表单绑定的方式各有其优势。

 1. 使用响应式表单

响应式表单的能力封装在ReactiveFormsModule中,并且跟FormsModule同时包含在@angular/forms这个包中。

表单类的要点:

1.AbstractControl是FormControl、FormGroup、FormArray这三个实例表单类的抽象基类。它提供了他们的通用行为以及属性,其中就有observable。

2.FormControl在单个表单控件中检查值并验证状态。它负责将其通知给HTML表单控件(比如input这些)。

3.FormGroup负责AbstractControl实例的一个组的值与验证状态。组的属性包含了它们的子控件。你的组件的顶级表单就是一个FormGroup。

4.FormArray负责AbstractControl实例的数值索引数组的值与状态验证。

2. FormControl

最核心的指令就是FormControl,算是底层的ngModel,在模板标签中跟定义好的数据模型字段绑定起来,就像这样:

<h2>Hero Detail</h2>
<h3><i>Just a FormControl</i></h3>
<label class="center-block">Name:
 <input class="form-control" [formControl]="name">
</label>

同时在组件代码中需要将上例中这个name字段声明为FormControl类:

export class HeroDetailComponent1 {
 name = new FormControl();
}

3. FormGroup

将多个FormControl对象分组到FormGroup中,用来方便管理。定义方法如下:

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

export class HeroDetailComponent2 {
 heroForm = new FormGroup ({
  name: new FormControl()
 });
}

此时模板标签中也要分组来写:

<h2>Hero Detail</h2>
<h3><i>FormControl in a FormGroup</i></h3>
<form [formGroup]="heroForm" novalidate>
 <div class="form-group">
  <label class="center-block">Name:
   <input class="form-control" formControlName="name">
  </label>
 </div>
</form>

现在的效果就是可以从heroForm中实时读取到其值和一些附加的状态的变化。

可以在模板中添加两个标签来展示数据的更改:

<p>Form value: {{ heroForm.value | json }}</p>
<p>Form status: {{ heroForm.status | json }}</p>

4. FormBuilder

还有个新概念就是FormBuilder,是用来帮助创建表单类的:

1. 显示声明heroForm属性的类型为FormGroup,后面你将会初始化它

2. 注入FormBuilder到构造器中

3. 使用FormsBuilder添加新方法定义heroForm,叫做createForm。

4. 在构造器中执行createForm方法。

export class HeroDetailComponent3 {
 heroForm: FormGroup; // <--- heroForm is of type FormGroup

 constructor(private fb: FormBuilder) { // <--- inject FormBuilder
  this.createForm();
 }

 createForm() {
  this.heroForm = this.fb.group({
   name: '', // <--- the FormControl called "name"
  });
 }
}

上例中执行createForm方法即可动态快速的创建出表单类,其在一些表单类需要更改的场景下可以使用。

5. setValue和patchValue

这两个方法是真正给表单模型赋值用的。因为表单显示的数据与真实的底层数据肯定不能使同一个,否则表单输入数据一旦更改,源数据就被污染了,而这两个方法就是用来将源数据赋值到表单模型数据上的。

每当需要赋值时就可以调用,其中setValue必须准确赋值,并且会在数据不匹配时报告错误;而patchValue没有这么严格,但可以传一个对象,且不匹配时不会报告错误。

而我们要做的就是在ng2组件的ngOnChanges回调中手动执行setValue设置数据值。比如这样:

ngOnChanges()
  this.heroForm.setValue({
   name:  this.hero.name,
   address: this.hero.addresses[0] || new Address()
  });
 }

同时ng2还提供了一个reset方法来重新调用setValue方法(setValue本身好像只是用来一次性赋值的)。

6. FormArray

FormArray是用来对付FormGroups列表的,比如一个英雄有可能有多个address字段,address字段本身就是个FormGroup,此时就要用到FormArray了:

this.heroForm = this.fb.group({
 name: ['', Validators.required ],
 secretLairs: this.fb.array([]), // <-- secretLairs as an empty FormArray
 power: '',
 sidekick: ''
});

获取FormArray要用到FormGroup提供的一个get方法:

get secretLairs(): FormArray {
 return this.heroForm.get('secretLairs') as FormArray;
};

其显示的模板如下:

<div formArrayName="secretLairs" class="well well-lg">
 <div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
  <!-- The repeated address template -->
 </div>
</div>

效果就是定义好这个FormArray以后就可以使用ngFor把这一个表单列表遍历出来了(直接使用ngModel加ngFor能省不少事情...)。

总结:

笔者目前使用ng2还没涉及到比较复杂的表单,所以对原文提到的内容理解并不是很深,加上老外写的文章通常都过于完整,都喜欢用长篇大论来说明一个简单的知识点,所以本文后半段其实没多少原文的影子,纯属笔者自己拙劣的概括,并且没有做过太多实践。

回看ng2的响应式表单能力,提供的指令以及服务也就这么几个(FormControl、FormGroup、FormArray、FormBuilder以及几个功能性方法),巧妙在使用这些能力就能完成一个强大的表单界面,其编码体验绝对是远超传统jQuery强行从DOM读取节点值的方式的,并且提供了除了简单的ngModel能力之外的更具体更强大的数据绑定方案,还有本文未提及的表单验证这个大内容,在ng2提供的这个响应式表单方案下实现起来也是很得心应手的。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
在线游戏大家来找茬II
Sep 30 Javascript
jquery焦点图片切换(数字标注/手动/自动播放/横向滚动)
Jan 24 Javascript
JS嵌套函数调用上下文的问题解决
Mar 26 Javascript
三种动态加载js的jquery实例代码另附去除js方法
Apr 30 Javascript
重写document.write实现无阻塞加载js广告(补充)
Dec 12 Javascript
js用拖动滑块来控制图片大小的方法
Feb 27 Javascript
javascript实现校验文件上传控件实例
Apr 20 Javascript
使用BootStrap进行轮播图的制作
Jan 06 Javascript
Angular2学习教程之组件中的DOM操作详解
May 28 Javascript
jquery实现企业定位式导航效果
Jan 01 jQuery
swiper在angularjs中使用循环轮播失效的解决方法
Sep 27 Javascript
js 解析 JSON 数据简单示例
Apr 21 Javascript
vue过渡和animate.css结合使用详解
Jun 14 #Javascript
ExtJs的Ext.Ajax.request实现waitMsg等待提示效果
Jun 14 #Javascript
详解Vue.use自定义自己的全局组件
Jun 14 #Javascript
详解vue-router2.0动态路由获取参数
Jun 14 #Javascript
微信小程序动态添加分享数据
Jun 14 #Javascript
vue实现百度搜索下拉提示功能实例
Jun 14 #Javascript
JS查找数组中重复元素的方法详解
Jun 14 #Javascript
You might like
表格展示无限级分类(PHP版)
2012/08/21 PHP
三个类概括PHP的五种设计模式
2012/09/05 PHP
Zend Framework教程之模型Model基本规则和使用方法
2016/03/04 PHP
thinkPHP5.0框架安装教程
2017/03/25 PHP
DHTML 中的绝对定位
2006/11/26 Javascript
javascript 异常处理使用总结
2009/06/21 Javascript
javascript 解决表单仍然提交即使监听处理函数返回false
2010/03/14 Javascript
遍历jquery对象的代码分享
2011/11/02 Javascript
node.js中的fs.readdirSync方法使用说明
2014/12/17 Javascript
JS解析XML文件和XML字符串详解
2015/04/17 Javascript
JavaScript表单验证实例之验证表单项是否为空
2016/01/10 Javascript
JS获取鼠标坐标位置实例分析
2016/01/20 Javascript
html5+CSS 实现禁止IOS长按复制粘贴功能
2016/12/28 Javascript
svg动画之动态描边效果
2017/02/22 Javascript
vue.js实例对象+组件树的详细介绍
2017/10/20 Javascript
React Native 自定义下拉刷新上拉加载的列表的示例
2018/03/01 Javascript
利用Console来Debug的10个高级技巧汇总
2018/03/26 Javascript
JavaScript实现邮箱后缀提示功能的示例代码
2018/12/13 Javascript
Vue利用Blob下载原生二进制数组文件
2019/09/25 Javascript
VSCode插件安装完成后的配置(常用配置)
2020/08/24 Javascript
[10:18]2018DOTA2国际邀请赛寻真——找回自信的TNCPredator
2018/08/13 DOTA
[01:11:37]完美世界DOTA2联赛PWL S2 SZ vs FTD.C 第一场 11.19
2020/11/19 DOTA
Python正则简单实例分析
2017/03/21 Python
python实现名片管理器的示例代码
2019/12/17 Python
python对接ihuyi实现短信验证码发送
2020/05/10 Python
Python虚拟环境库virtualenvwrapper安装及使用
2020/06/17 Python
canvas学习总结三之绘制路径-线段
2019/01/31 HTML / CSS
周仰杰(JIMMY CHOO)英国官方网站:闻名世界的鞋子品牌
2018/10/28 全球购物
意大利一家专营包包和配饰的网上商店:Borse Last Minute
2019/08/26 全球购物
什么是Rollback Segment
2013/04/22 面试题
大学生职业生涯规划书模板
2014/01/18 职场文书
优秀工作者事迹材料
2014/12/26 职场文书
送达通知书
2015/04/25 职场文书
2015年世界急救日宣传活动方案
2015/05/06 职场文书
九年级英语教学反思
2016/02/15 职场文书
Redis持久化与主从复制的实践
2021/04/27 Redis