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模板的代碼,把驗證邏輯抽離到組件類中
方便的跟蹤表單控件值的變化
易於單元測試