Angular 4.x 中有两种表单:html
Template-Driven Forms - 模板驱动式表单 (相似于 AngularJS 1.x 中的表单 )react
Reactive Forms (Model-Driven Forms) - 响应式表单typescript
Template-Driven Forms (模板驱动表单) ,咱们以前的文章已经介绍过了,了解详细信息,请查看 - Angular 4.x Template-Driven Forms 。json
ngModule and reactive formsbootstrap
FormControl and FormGroupsegmentfault
Implementing our FormGroup model数组
Binding our FormGroup model安全
Reactive submitapp
Reactive error validation单元测试
Simplifying with FormBuilder
<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 输入框的值
为全部输入框添加表单验证功能
显示验证异常信息
表单验证失败时,不容许进行表单提交
表单提交功能
// signup.interface.ts export interface User { name: string; account: { email: string; confirm: string; } }
在咱们继续深刻介绍 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。
咱们将基于上面的定义的基础表单,建立 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 和 FormGroup 的概念:
FormControl - 它是一个为单个表单控件提供支持的类,可用于跟踪控件的值和验证状态,此外还提供了一系列公共API。
使用示例:
ngOnInit() { this.myControl = new FormControl('Semlinker'); }
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'
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 接口来获取表单的初始信息。
如今咱们已经实例化了 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: '' }}
跟模板驱动的表单同样,咱们能够经过 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); } }
表单的数据绑定方式和提交逻辑已经介绍完了,是该介绍表单实际应用中,一个重要的环节 — 表单验证。
接下来咱们来为表单添加验证规则,首先咱们须要从 @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
,来简化上面的操做。
首先咱们须要从 @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 (模板驱动表单) 的特色
使用方便
适用于简单的场景
经过 [(ngModel)] 实现数据双向绑定
最小化组件类的代码
不易于单元测试
Reactive Forms (响应式表单) 的特色
比较灵活
适用于复杂的场景
简化了HTML模板的代码,把验证逻辑抽离到组件类中
方便的跟踪表单控件值的变化
易于单元测试