iT邦幫忙

0

[Angular] Forms - Reactive Forms

前言

Reactive forms提供了一種model-driven的方法來處理表單中會隨時間變化的輸入(數據),本篇中介紹著如何控制與更新一個表單、使用多個form-group、表單驗證等等的基本表單操作。


Overview of reactive forms

Reactive forms使用了顯式且不變的方法管理表單狀況,對於表單的狀態的更改每次都會回傳一個新的表單狀態,由於每次都會回傳一個新的狀態,所以他可以保持每次該改之間的model完整性,Reactive forms是圍繞著observable所建立的,表單中的輸入(數值)會提供給input flow作為他的值並可以以同步的方式訪問。

Reactive forms與template forms不同,Reactive forms通過對數據模型的同步訪問提供了較高的可預測性observable operators的不變性可以透過observable streams觀察數據的變更

Adding a basic form control

建立Reactive forms的三個步驟:

  1. 在你的app中註冊reactive foms module,這個module中含有使用Reactive forms的指令。
  2. 在Component中新增一個新的FormControl instance
  3. 在templete中註冊FormControl。

Register the reactive forms module

使用import關鍵字將ReactiveFormsModule添加到app.module中的import陣列中。

// src/app/app.module.ts 

import { ReactiveFormsModule } from '@angular/forms';

@NgModule({
  imports: [
    // other imports ...
    ReactiveFormsModule
  ],
})
export class AppModule { }

接下來透過cli建立一個 name-editor Component,並將FormControlclass加入到Component中並將他實體化。

// src/app/name-editor/name-editor.component.ts

import { Component } from '@angular/core';
import { FormControl } from '@angular/forms'; // import FormControl Class

@Component({
  selector: 'app-name-editor',
  templateUrl: './name-editor.component.html',
  styleUrls: ['./name-editor.component.css']
})
export class NameEditorComponent {
  name = new FormControl(''); // instance FormControl class
}

Register the control in the template

name-editor的templete中建立可以與表單控制元件連動的網頁元件,使用formControl綁定templete中的元件。

<!-- src/app/name-editor/name-editor.component.html -->

<label>
  Name:
  <input type="text" [formControl]="name">
</label>

在畫面中顯示出來

<!-- src/app/app.component.html  -->

<app-name-editor></app-name-editor>

Displaying a form control value

可以透過兩種方法顯示forms中的數值:

  1. 可以透過valueChange()中的subscription()方法監聽表單中的值。
  2. 使用FormControl中的value屬性。
name = new FormControl('');

this.name.valueChanges.subscribe(val => console.log(val));
Value : {{name.value}}

Grouping form controls

一個表單中會包含幾個相關的控制元件,Reactive forms提供兩種方法可以將多個相關的控制組件分組為單個輸入形式

  • 使用form group定義一組固定的控制組件表單,可以統一管理這些類似的表單組件。
  • 使用form array定義一個動態的表單,可以隨時添加或刪除控制組件。

透過cli建立一個新組建,使用import將FormGroupFormControl加入Component中並將他們實例化。

// src/app/profile-editor/profile-editor.component.ts (form group)

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms'; //import two class

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  // instance FormGroup & FormControl
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
}

firstNamelastName這兩個相關的控制組件透過FormGroup分組為一個群組。

Create a nested groups

可以透過嵌套FromGroups來達到將較為複雜的表單內容分割為更小的Group,以方便管理以維護。

import { Component } from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
}

將原本的profileForm Group中新增了第二層的嵌套Group,將street、city、state、zip綁定為一個FormGroup,透過這樣的綁定可以將原本的profileForm保持完整的前提下再新增出一個Group。

<form [formGroup]="profileForm">
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>
  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>
  
  <div formGroupName="address">
      <h3>Address</h3>
      <label>
        Street:
        <input type="text" formControlName="street">
      </label>
      <label>
        City:
        <input type="text" formControlName="city">
      </label>
      <label>
        State:
        <input type="text" formControlName="state">
      </label>
      <label>
        Zip Code:
        <input type="text" formControlName="zip">
      </label>
  </div>

  <button type="submit" [disabled]="!profileForm.valid">Submit</button>
</form>

Updating parts of the data model

當需要更新多個控制組件的值時,Angular提供兩種方法可以改變值,一種是可以更改整組表單的方法,另一種是更改表單部分內容的方法。

  • 使用setValue()可以更新整個表單內容,他必須要遵守整個表單的結構
  • 使用patchValue()可以替換表單內的部分內容。

使用setValue()會嚴格檢查整個表單的結構,若結構錯誤則會發生嵌套錯誤,若是使用patchValue()在這個錯誤上會默默地失敗。

updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}

Using the FormBuilder service to generate controls

對於在處理多個表單的時候還是使用手動的方式創建表單控制組件實例,這樣的話可能會遇到重複宣告的問題,所以可以使用FormBuilder,它提供了用於生成控制組件的便利方法。

  1. import FormBuilder Class.
  2. Inject the FormBuilder service.
  3. Generate the form contents.

Import the FormBuilder class

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

Inject the FormBuilder service

在Component的comstructor中Inject FormBuilder service

constructor(private fb: FormBuilder) { }

Generate form controls

FormBuilder提供control()group()array()這三個方法,他們用於生成實例包括FormControl、FormGroup、FromArray。

import { Component } from '@angular/core';
import { FormBuilder } from '@angular/forms';

@Component({
  selector: 'app-profile-editor',
  templateUrl: './profile-editor.component.html',
  styleUrls: ['./profile-editor.component.css']
})
export class ProfileEditorComponent {
  profileForm = this.fb.group({
    firstName: [''],
    lastName: [''],
    address: this.fb.group({
      street: [''],
      city: [''],
      state: [''],
      zip: ['']
    }),
  });

  constructor(private fb: FormBuilder) { }
}

Creating dynamic forms

FormArray是用於管理任何數量的未命名控件的FormGroup的替代方法,可以動態的插入或刪除,所以不知道子值的數量時可以使用FormArray來代替FormGroup。

  1. Import the FormArray class.
  2. Define a FormArray control.
  3. 使用get()訪問FormArray control.
  4. Display the form array in a template.

Import the FormArray class

import { FormArray } from '@angular/forms';

Define a FormArray control

通過在數組中定義控件,可以使用從零到許多的任意數量的控件來初始化表單數組,使用FormBuilder.array()定義陣列並使用FormBuilder.control()初始化控件填充數組。

profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
  aliases: this.fb.array([
    this.fb.control('')
  ])
});

Access the FormArray control

可以使用getter可以輕鬆訪問表單陣列的實例的別名。

get aliases() {
  return this.profileForm.get('aliases') as FormArray;
}

還可以使用FormArray.push()將表單數據動態的插入到現有表單中。

addAlias() {
  this.aliases.push(this.fb.control(''));
}

Display the form array in the template

<div formArrayName="aliases">
  <h3>Aliases</h3> <button (click)="addAlias()">Add Alias</button>

  <div *ngFor="let alias of aliases.controls; let i=index">
    <!-- The repeated alias template -->
    <label>
      Alias:
      <input type="text" [formControlName]="i">
    </label>
  </div>
</div>

使用*ngFor遍歷aliases中的所有表單實例,因為表單數組元素未命名,所以您將索引分配給i變量,並將其傳遞給每個控件以將其綁定到formControlName輸入。


Reactive forms API summary

Classes

Class Description
AbstractControl FormControl,FormGroup和FormArray的抽象基類,它提供了常見的行為和屬性。
FormControl 管理單個表單空間的值和有效性的狀態,他對應到HTML的表單控件<input><select>
FormGroup 管理一組AbstractControl實例的值和有效狀態,它包含他這個FormGroup的子FormControl。
FormArray 管理AbstractControl實例的陣列的值有效性。
FormBuilder 用於創建FormControl的方法。

Directives

Directive Description
FormControlDirective 將獨立的FormControl實例同步到表單控件元素。
FormControlName 通過名稱將現有FormGroup實例中的FormControl同步到表單控件元素。
FormGroupDirective 將現有的FormGroup實例同步到DOM元素。
FormGroupName 將嵌套的FormGroup實例同步到DOM元素。
FormArrayName 將嵌套的FormArray實例同步到DOM元素。

參考文獻:
Angular - Reactive Forms


尚未有邦友留言

立即登入留言