angular2-響應式表單

響應式表單是同步的。模板驅動表單是異步的。這個不一樣點很重要javascript

使用響應式表單,咱們會在代碼中建立整個表單控件樹。 咱們能夠當即更新一個值或者深刻到表單中的任意節點,由於全部的控件都始終是可用的。java

模板驅動表單會委託指令來建立它們的表單控件。 爲了消除「檢查完後又變化了」的錯誤,這些指令須要消耗一個以上的變動檢測週期來構建整個控件樹。 這意味着在從組件類中操縱任何控件以前,咱們都必須先等待一個節拍。react

好比,若是咱們用  @ViewChild(NgForm查詢來注入表單控件,並在 生命週期鉤子  ngAfterViewInit  中檢查它,就會發現它沒有子控件。 咱們必須使用  setTimeout 等待一個節拍才能從控件中提取值、測試有效性,或把它設置爲新值。api

 

導入ReactiveFormsModule

 

基礎的表單類

  • AbstractControl是三個具體表單類的抽象基類。 併爲它們提供了一些共同的行爲和屬性,其中有些是可觀察對象(Observable)數組

  • FormControl 用於跟蹤一個單獨的表單控件的值和有效性狀態。它對應於一個HTML表單控件,好比輸入框和下拉框。瀏覽器

  • FormGroup用於 跟蹤一組AbstractControl的實例的值和有效性狀態。 該組的屬性中包含了它的子控件。 組件中的頂級表單就是一個FormGroup服務器

  • FormArray用於跟蹤AbstractControl實例組成的有序數組的值和有效性狀態。異步

 

添加FormGroup

多個FormControl,咱們會但願把它們註冊進一個父FormGroup中。這很容易。只要把它加入hero-detail.component.tsimport區就能夠了ide

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的住址。

JSON output

 

查看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();
}
相關文章
相關標籤/搜索