响应式表单是同步的。模板驱动表单是异步的。这个不同点很重要

使用响应式表单,我们会在代码中创建整个表单控件树。 我们可以立即更新一个值或者深入到表单中的任意节点,因为所有的控件都始终是可用的。

模板驱动表单会委托指令来创建它们的表单控件。 为了消除“检查完后又变化了”的错误,这些指令需要消耗一个以上的变更检测周期来构建整个控件树。 这意味着在从组件类中操纵任何控件之前,我们都必须先等待一个节拍。

比如,如果我们用  @ViewChild(NgForm查询来注入表单控件,并在 生命周期钩子  ngAfterViewInit  中检查它,就会发现它没有子控件。 我们必须使用  setTimeout 等待一个节拍才能从控件中提取值、测试有效性,或把它设置为新值。

导入ReactiveFormsModule

基础的表单类

  • AbstractControl是三个具体表单类的抽象基类。 并为它们提供了一些共同的行为和属性,其中有些是可观察对象(Observable)

  • FormControl 用于跟踪一个单独的表单控件的值和有效性状态。它对应于一个HTML表单控件,比如输入框和下拉框。

  • FormGroup用于 跟踪一组AbstractControl的实例的值和有效性状态。 该组的属性中包含了它的子控件。 组件中的顶级表单就是一个FormGroup

  • FormArray用于跟踪AbstractControl实例组成的有序数组的值和有效性状态。

添加FormGroup

多个FormControl,我们会希望把它们注册进一个父FormGroup中。这很容易。只要把它加入hero-detail.component.tsimport区就可以了

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

  

<form [formGroup]="heroForm" novalidate>
  <div class="form-group">
    <label class="center-block">Name:
      <input class="form-control" formControlName="name">
    </label>
  </div>
</form>

<form>元素上的novalidate属性会阻止浏览器使用原生HTML中的表单验证器。

formGroup是一个响应式表单的指令,它拿到一个现有FormGroup实例,并把它关联到一个HTML元素上。 这种情况下,它关联到的是form元素上的FormGroup实例heroForm

  heroForm.value   ==>  { name : xxxx, ....... }

FormBuilder

FormBuilder类能通过处理控件创建的细节问题来帮我们减少重复劳动。

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

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"
    });
  }
}

FormBuilder.group是一个用来创建FormGroup的工厂方法,它接受一个对象,对象的键和值分别是FormControl的名字和它的定义。 在这个例子中,name控件的初始值是空字符串。

要想让name这个FormControl是必须的,请把FormGroup中的name属性改为一个数组。第一个条目是name的初始值,第二个是required验证器:Validators.required

this.heroForm = this.fb.group({
  name: ['', Validators.required ],
});

  

多级FormGroup

export class HeroDetailComponent5 {
  heroForm: FormGroup;
  states = states;

  constructor(private fb: FormBuilder) {
    this.createForm();
  }

  createForm() {
    this.heroForm = this.fb.group({ // <-- the parent FormGroup
      name: ['', Validators.required ],
      address: this.fb.group({ // <-- the child FormGroup
        street: '',
        city: '',
        state: '',
        zip: ''
      }),
      power: '',
      sidekick: ''
    });
  }
}
<div formGroupName="address" class="well well-lg">
  <input class="form-control" formControlName="street">
  <input class="form-control" formControlName="city">
  <select class="form-control" formControlName="state">
  <input class="form-control" formControlName="zip">
</div>

做完这些之后,浏览器中的JSON输出就变成了带有多级FormGroup的住址。

查看FormControl的属性

使用.get()方法来提取表单中一个单独FormControl的状态

<p>Name value: {{ heroForm.get('name').value }}</p>

要点取得FormGroup中的FormControl的状态,使用点语法来指定到控件的路径

<p>Street value: {{ heroForm.get('address.street').value}}</p>

使用此技术来显示FromControl任意属性,代码如下

属性

说明

myControl.value

FormControl的值。

myControl.status

FormControl的有效性。可能的值有VALIDINVALIDPENDINGDISABLED

myControl.pristine

如果用户尚未改变过这个控件的值,则为true。它总是与myControl.dirty相反。

myControl.untouched

true 如果用户尚未进入这个HTML控件,也没有触发过它的blur(失去焦点)事件,则为true。 它是myControl.touched的反义词。

数据模型表单模型

自服务器的 hero 就是数据模型,而FormControl的结构就是表单模型

组件必须把数据模型中的英雄值复制到表单模型中。这里隐含着两个非常重要的点。

    1. 开发人员必须理解数据模型是如何映射到表单模型中的属性的。

    2. 用户修改时的数据流是从DOM元素流向表单模型的,而不是数据模型。表单控件永远不会修改数据模型

// 数据模型
export class Hero {
  id = 0;
  name = '';
  addresses: Address[];
}

export class Address {
  street = '';
  city   = '';
  state  = '';
  zip    = '';
}

//表单模型
this.heroForm = this.fb.group({
  name: ['', Validators.required ],
  address: this.fb.group(new Address()),
  power: '',
  sidekick: ''
});

在这些模型中有两点显著的差异:

    1. Hero有一个id。表单模型中则没有,因为我们通常不会把主键展示给用户。

    2. Hero有一个住址数组。这个表单模型只表示了一个住址,稍后的修改则可以表示多个。

使用setValuepatchValue来操纵表单模型

setValue 方法

this.heroForm.setValue({
  name:    this.hero.name,
  address: this.hero.addresses[0] || new Address()  //(只能显示英雄的第一个住址,不过我们还必须考虑hero完全没有住址的可能性)
});

setValue方法会在赋值给任何表单控件之前先检查数据对象的值。

它不会接受一个与FormGroup结构不同或缺少表单组中任何一个控件的数据对象。

如果我们有什么拼写错误或控件嵌套的不正确,它就能返回一些有用的错误信息。 patchValue会默默地失败

patchValue 方法

借助patchValue,我们可以通过提供一个只包含要更新的控件的键值对象来把值赋给FormGroup中的指定控件

this.heroForm.patchValue({
  name: this.hero.name
});

  

什么时候设置表单的模型值(ngOnChanges

我们可以在ngOnChanges钩子中调用setValue,就像例子中所演示的那样, 每当输入属性hero发生变化时,Angular就会调用它。

重置表单的标识。

我们应该在更换英雄的时候重置表单,以便来自前一个英雄的控件值被清除,并且其状态被恢复为pristine(原始)状态。 我们可以在ngOnChanges的顶部调用reset,就像这样

this.heroForm.reset();

reset方法有一个可选的state值,让我们能在重置状态的同时顺便设置控件的值。 在内部实现上,reset会把该参数传给了setValue。 略微重构之后,ngOnChanges会变成这样

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

  

使用FormArray来表示FormGroup数组

Hero.addresses属性就是一个Address实例的数组。 一个住址的FormGroup可以显示一个Address对象。 而FormArray可以显示一个住址FormGroup的数组

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

要想使用FormArray,我们要这么做:

    1. 在数组中定义条目(FormControlFormGroup)。

    2. 把这个数组初始化微一组从数据模型中的数据创建的条目。

    3. 根据用户的需求添加或移除这些条目。

从用户的视角来看,英雄们没有住址。 只有我们凡人才有住址,英雄们拥有的是秘密小屋! 把FormGroup型的住址替换为FormArray型的secretLairs定义:

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

  

表单的控件名从address改为secretLairs让我们遇到了一个重要问题:表单模型数据模型不再匹配了。

显然,必须在两者之间建立关联。但它在应用领域中的意义不限于此,它可以用于任何东西。

展现的需求经常会与数据的需求不同。 响应式表单的方法既强调这种差异,也能为这种差异提供了便利。

初始化FormArray型的secretLairs

下面的setAddresses方法把secretLairs数组替换为一个新的FormArray,使用一组表示英雄地址的FormGroup来进行初始化。

setAddresses(addresses: Address[]) {
  const addressFGs = addresses.map(address => this.fb.group(address));
  const addressFormArray = this.fb.array(addressFGs);
  this.heroForm.setControl('secretLairs', addressFormArray);
}

注意,我们使用FormGroup.setControl方法,而不是setValue方法来设置前一个FormArray。 我们所要替换的是控件,而不是控件的。  

获取FormArray

使用FormGroup.get方法来获取到FormArray的引用。 把这个表达式包装进一个名叫secretLairs的便捷属性中来让它更清晰,并供复用

this.heroForm = this.fb.group({
  name: '',
  secretLairs: this.fb.array([]),
  power: '',
  sidekick: ''
});

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

  

显示FormArray

当前HTML模板显示单个的地址FormGroup。 我们要把它修改成能显示0、1或更多个表示英雄地址的FormGroup

要改的部分主要是把以前表示地址的HTML模板包裹进一个<div>中,并且使用*ngFor来重复渲染这个<div>

诀窍在于要知道如何编写*ngFor。主要有三点:

    1. *ngFor<div>之外套上另一个包装<div>,并且把它的formArrayName指令设为"secretLairs"。 这一步为内部的表单控件建立了一个FormArray型的secretLairs作为上下文,以便重复渲染HTML模板。

    2. 这些重复条目的数据源是FormArray.controls而不是FormArray本身。 每个控件都是一个FormGroup型的地址对象,与以前的模板HTML所期望的格式完全一样。

    3. 每个被重复渲染的FormGroup都需要一个独一无二的formGroupName,它必须是FormGroup在这个FormArray中的索引。 我们将复用这个索引,以便为每个地址组合出一个独一无二的标签。

<div formArrayName="secretLairs" class="well well-lg">
  <div *ngFor="let address of secretLairs.controls; let i=index" [formGroupName]="i" >
    <input class="form-control" formControlName="street">
    <input class="form-control" formControlName="city">
    <select class="form-control" formControlName="state">
    <input class="form-control" formControlName="zip">
  </div>
</div>

  

添加一个addLair方法,它获取secretLairs数组,并把新的表示地址的FormGroup添加到其中。

addLair() {
  this.secretLairs.push(this.fb.group(new Address()));
}

<button (click)="addLair()" type="button">Add a Secret Lair</button>

//务必确保添加了type="button"属性。 事实上,我们应该总是指定按钮的type。 如果不明确指定类型,按钮的默认类型就是“submit”(提交)。 当我们稍后添加了表单提交的动作时,每个“submit”按钮都是触发一次提交操作,而它将可能会做一些处理,比如保存当前的修改。 我们显然不会希望每当用户点击“Add a Secret Lair”按钮时就保存一次。

 

监视控件的变化

用户在父组件HeroListComponent中选取了一个英雄,Angular就会调用一次ngOnChanges

用户修改英雄的名字秘密小屋时,Angular并不会调用ngOnChanges

通过订阅表单控件的属性之一来了解这些变化,此属性会发出变更通知,  valueChanges,可以返回一个RxJS的Observable对象。

添加下列方法,以监听姓名这个FormControl中值的变化

nameChangeLog: string[] = [];
logNameChange() {
  const nameControl = this.heroForm.get('name');
  nameControl.valueChanges.forEach(
    (value: string) => this.nameChangeLog.push(value)
  );
}

在构造函数中调用它,就在创建表单的代码之后

constructor(private fb: FormBuilder) {
  this.createForm();
  this.logNameChange();
}

  

angular2-响应式表单的更多相关文章

  1. Angular2响应式表单

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  2. Angular2响应式表单-翻译与概括官网REACTIVE FORMS页面

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  3. Angular11 模板表单、响应式表单(自定义验证器)、HTTP、表单元素双向绑定

    1 模板表单 模型通过指令隐式创建 技巧01:需要在模块级别引入 FormsModule ,通常在共享模块中引入再导出,然后在需要用到 FormsModule 的模块中导入共享模块就可以啦 impor ...

  4. ng2响应式表单-翻译与概括官网REACTIVE FORMS页面

    本文将半翻译半总结的讲讲ng2官网的另一个未翻译高级教程页面. 原文地址. 文章目的是使用ng2提供的响应式表单技术快速搭出功能完善丰富的界面表单组件. 响应式表单是一项响应式风格的ng2技术,本文将 ...

  5. Angular Reactive Forms -- Model-Driven Forms响应式表单

    Angular 4.x 中有两种表单: Template-Driven Forms - 模板驱动式表单 (类似于 AngularJS 1.x 中的表单 )  官方文档:https://v2.angul ...

  6. angular6的响应式表单

    1:在AppModule模块里面引入 ReactiveFormsModule 要使用响应式表单,就要从@angular/forms包中导入ReactiveFormsModule,并把它添加到你的NgM ...

  7. Angular之响应式表单 ( Reactive Forms )

    项目结构 一 首页 ( index.html ) <!doctype html> <html lang="en"> <head> <met ...

  8. angular响应式表单 - 状态字段

    用于表单验证的过程: touched,untoched pristine,dirty pending

  9. 【译】用 Chart.js 做漂亮的响应式表单

    数据包围着我们.虽然搜索引擎和其他应用都对基于文本方式表示的数据偏爱有加,但人们发现可视化是更容易理解的一种方式.今年初,SitePoint 发表了 Aurelio 的文章< Chart.js简 ...

  10. angular 响应式表单(登录实例)

    一.表单验证 1. 只有一个验证规则: this.myGroup = this.fb.group({ email:['hurong.cen@qq.com',Validators.required], ...

随机推荐

  1. 一款给力的一键复制js插件-clipboard.js

    一款没有依赖的.给力的一键复制的JS插件   点我前往github 案例demo见下载包内demo文件夹. 这里晒出最常用的几种方式,以供不时之需. <!DOCTYPE html> < ...

  2. Eclipse的一下设置

    一.设置自动补全 1.打开 Eclipse -> Window(窗口) -> Perferences(首选项) 2.点开java->Editor(编辑器)->Content A ...

  3. Xilinx FPGA使用——ROM初始化文件

    在调用ROM的IP Core时,需要对其进行初始化,利用MATLAB生成其初始化数据文件. 工具:ISE 14.7.MATLAB.notepad++ 废话不多说,直接上MATLAB代码,生成了一个10 ...

  4. python学习之路---day04

    一:元组 元组案例:tuple=("张三","李四","王五","小六","大七",["1 ...

  5. Hero

    Time Limit:3000MS     Memory Limit:65536KB     64bit IO Format:%I64d & %I64u Description When pl ...

  6. POJ3635 Full Tank? 优先队列BFS or 分层图最短路 or DP?

    然而我也不知道这是啥啊...反正差不多...哪位大佬给区分一下QWQ.. 好的,我把堆的<写反了..又调了一个小时..你能不能稳一点.... 记录状态:所在位置u,油量c,花费w 扩展状态: 1 ...

  7. vim大法

    $Linux vi/vim编辑器常用命令与用法总结 (一)vi/vim是什么?Linux世界几乎所有的配置文件都是以纯文本形式存在的,而在所有的Linux发行版系统上都有vi编辑器,因此利用简单的文字 ...

  8. 基于scrapy的一些实例

    一.爬取斗鱼主播 1. 爬虫文件 # -*- coding: utf-8 -*- import scrapy import json from Douyu.items import DouyuItem ...

  9. aoj0033

    一.题意:有十个数,判断是否能分成两个递增序列 二.思路: 1.dfs:每个数判断在左边或者右边,遍历所有情况. 2.贪心:在保证递增序的前提下,判断一个数放左边或者右边,决定于其更接近于哪一边最上面 ...

  10. WPF Binding的值转换器

    注意:值转换器中用于传入额外信息的参数 parameter 在 Binding 时使用 Binding 对象的 ConverterParameter 属性指定,但是设置了 ConverterParam ...