详解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 相关文章推荐
慎用 somefunction.prototype 分析
Jun 02 Javascript
(jQuery,mootools,dojo)使用适合自己的编程别名命名
Sep 14 Javascript
javascript中定义类的方法详解
Feb 10 Javascript
JS实现横向与竖向两个选项卡Tab联动的方法
Sep 27 Javascript
jquery操作checkbox火狐下第二次无法勾选的解决方法
Oct 10 Javascript
Javascript DOM事件操作小结(监听鼠标点击、释放,悬停、离开等)
Jan 20 Javascript
微信小程序中的onLoad详解及简单实例
Apr 05 Javascript
DataTables添加额外的查询参数和删除columns等无用参数实例
Jul 04 Javascript
vue-router 路由基础的详解
Oct 17 Javascript
angularJs中json数据转换与本地存储的实例
Oct 08 Javascript
微信小程序 高德地图路线规划实现过程详解
Aug 05 Javascript
vue2.x 对象劫持的原理实现
Apr 19 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
第十二节 类的自动加载 [12]
2006/10/09 PHP
php magic_quotes_gpc的一点认识与分析
2008/08/18 PHP
PHP print类函数使用总结
2010/06/25 PHP
PHP 对象继承原理与简单用法示例
2020/04/21 PHP
PHP Pipeline 实现中间件的示例代码
2020/04/26 PHP
Javascript-Mozilla和IE中的一个函数直接量的问题分析
2007/08/12 Javascript
javascript实现的距离现在多长时间后的一个格式化的日期
2009/10/29 Javascript
JavaScript学习笔记(十七)js 优化
2010/02/04 Javascript
浅谈JavaScript编程语言的编码规范
2011/10/21 Javascript
Jquery图片延迟加载插件jquery.lazyload.js的使用方法
2014/05/21 Javascript
原生js实现fadein 和 fadeout淡入淡出效果
2014/06/05 Javascript
jQuery实现复选框全选/取消全选/反选及获得选择的值
2014/06/12 Javascript
一个JavaScript操作元素定位元素的实例
2014/10/29 Javascript
jQuery中removeClass()方法用法实例
2015/01/05 Javascript
Javascript 实现简单计算器实例代码
2016/10/23 Javascript
详解微信开发中snsapi_base和snsapi_userinfo及静默授权的实现
2017/03/11 Javascript
JavaScript常用工具方法封装
2019/02/12 Javascript
JS实现判断有效的数独算法示例
2019/02/25 Javascript
Nodejs中怎么实现函数的串行执行
2019/03/02 NodeJs
微信小程序仿抖音短视频切换效果的实例代码
2020/06/24 Javascript
Python3读取Excel数据存入MySQL的方法
2018/05/04 Python
python获取服务器响应cookie的实例
2018/12/28 Python
Python基于matplotlib画箱体图检验异常值操作示例【附xls数据文件下载】
2019/01/07 Python
Linux上使用Python统计每天的键盘输入次数
2019/04/17 Python
Python中顺序表原理与实现方法详解
2019/12/03 Python
python3 webp转gif格式的实现示例
2019/12/10 Python
python第三方库学习笔记
2020/02/07 Python
python日志通过不同的等级打印不同的颜色(示例代码)
2021/01/13 Python
彻底弄明白CSS3的Media Queries(跨平台设计)
2010/07/27 HTML / CSS
HTML5头部标签的一些常用信息小结
2016/10/23 HTML / CSS
移动端HTML5 input常见问题(小结)
2020/09/28 HTML / CSS
牵手50新加坡:专为黄金岁月的单身人士而设的交友网站
2020/08/16 全球购物
尽职尽责村干部自我鉴定
2014/01/23 职场文书
《果园机器人》教学反思
2014/04/13 职场文书
打架检讨书
2015/01/27 职场文书
2015年中学图书馆工作总结
2015/07/22 职场文书