填寫這份《一分鐘調查》,幫我們(開發組)做得更好!去填寫Home

響應式表單

Reactive forms

響應式表單提供了一種模型驅動的方式來處理表單輸入,其中的值會隨時間而變化。本文會向你展示如何建立和更新基本的表單控制元件,接下來還會在一個表單組中使用多個控制元件,驗證表單的值,以及建立動態表單,也就是在執行期新增或移除控制元件。

Reactive forms provide a model-driven approach to handling form inputs whose values change over time. This guide shows you how to create and update a basic form control, progress to using multiple controls in a group, validate form values, and create dynamic forms where you can add or remove controls at run time.

試試這個響應式表單的現場演練響應式表單的現場演練 / 下載範例

Try thisReactive Forms live-exampleReactive Forms live-example / 下載範例.

先決條件

Prerequisites

在深入瞭解被動表單之前,你應該對這些內容有一個基本的瞭解:

Before going further into reactive forms, you should have a basic understanding of the following:

響應式表單概述

Overview of reactive forms

響應式表單使用顯式的、不可變的方式,管理表單在特定的時間點上的狀態。對表單狀態的每一次變更都會返回一個新的狀態,這樣可以在變化時維護模型的整體性。響應式表單是圍繞 Observable 流建構的,表單的輸入和值都是透過這些輸入值組成的流來提供的,它可以同步訪問。

Reactive forms use an explicit and immutable approach to managing the state of a form at a given point in time. Each change to the form state returns a new state, which maintains the integrity of the model between changes. Reactive forms are built around observable streams, where form inputs and values are provided as streams of input values, which can be accessed synchronously.

響應式表單還提供了一種更直觀的測試路徑,因為在請求時你可以確信這些資料是一致的、可預料的。這個流的任何一個消費者都可以安全地操縱這些資料。

Reactive forms also provide a straightforward path to testing because you are assured that your data is consistent and predictable when requested. Any consumers of the streams have access to manipulate that data safely.

響應式表單與範本驅動表單有著顯著的不同點。響應式表單透過對資料模型的同步訪問提供了更多的可預測性,使用 Observable 的運算子提供了不可變性,並且透過 Observable 流提供了變化追蹤功能。

Reactive forms differ from template-driven forms in distinct ways. Reactive forms provide more predictability with synchronous access to the data model, immutability with observable operators, and change tracking through observable streams.

範本驅動表單允許你直接在範本中修改資料,但不像響應式表單那麼明確,因為它們依賴嵌入到範本中的指令,並藉助可變資料來非同步追蹤變化。參閱表單概覽以瞭解這兩種正規化之間的詳細比較。

Template-driven forms allow direct access to modify data in your template, but are less explicit than reactive forms because they rely on directives embedded in the template, along with mutable data to track changes asynchronously. See the Forms Overview for detailed comparisons between the two paradigms.

新增基礎表單控制元件

Adding a basic form control

使用表單控制元件有三個步驟。

There are three steps to using form controls.

  1. 在你的應用中註冊響應式表單模組。該模組聲明瞭一些你要用在響應式表單中的指令。

    Register the reactive forms module in your app. This module declares the reactive-form directives that you need to use reactive forms.

  2. 產生一個新的 FormControl 實例,並把它儲存在元件中。

    Generate a new FormControl instance and save it in the component.

  3. 在範本中註冊這個 FormControl

    Register the FormControl in the template.

然後,你可以把元件新增到範本中來顯示表單。

You can then display the form by adding the component to the template.

下面的例子展示了如何新增一個表單控制元件。在這個例子中,使用者在輸入欄位中輸入自己的名字,捕獲其輸入值,並顯示表單控制元件的當前值。

The following examples show how to add a single form control. In the example, the user enters their name into an input field, captures that input value, and displays the current value of the form control element.

註冊響應式表單模組

Register the reactive forms module

要使用響應式表單控制元件,就要從 @angular/forms 套件中匯入 ReactiveFormsModule,並把它新增到你的 NgModule 的 imports 陣列中。

To use reactive form controls, import ReactiveFormsModule from the @angular/forms package and add it to your NgModule's imports array.

import { ReactiveFormsModule } from '@angular/forms'; @NgModule({ imports: [ // other imports ... ReactiveFormsModule ], }) export class AppModule { }
src/app/app.module.ts (excerpt)
      
      import { ReactiveFormsModule } from '@angular/forms';

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

產生一個新的 FormControl

Generate a new FormControl

使用 CLI 命令 ng generate 在專案中產生一個元件作為該表單控制元件的宿主。

Use the CLI command ng generate to generate a component in your project to host the control.

ng generate component NameEditor
      
      ng generate component NameEditor
    

要註冊一個表單控制元件,就要匯入 FormControl 類別並建立一個 FormControl 的新實例,將其儲存為類別的屬性。

To register a single form control, import the FormControl class and create a new instance of FormControl to save as a class property.

import { Component } from '@angular/core'; import { FormControl } from '@angular/forms'; @Component({ selector: 'app-name-editor', templateUrl: './name-editor.component.html', styleUrls: ['./name-editor.component.css'] }) export class NameEditorComponent { name = new FormControl(''); }
src/app/name-editor/name-editor.component.ts
      
      import { Component } from '@angular/core';
import { FormControl } from '@angular/forms';

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

可以用 FormControl 的建構函式設定初始值,這個例子中它是空字串。透過在你的元件類別中建立這些控制元件,你可以直接對表單控制元件的狀態進行監聽、修改和校驗。

Use the constructor of FormControl to set its initial value, which in this case is an empty string. By creating these controls in your component class, you get immediate access to listen for, update, and validate the state of the form input.

在範本中註冊該控制元件

Register the control in the template

在元件類別中建立了控制元件之後,你還要把它和範本中的一個表單控制元件關聯起來。修改範本,為表單控制元件新增 formControl 繫結,formControl 是由 ReactiveFormsModule 中的 FormControlDirective 提供的。

After you create the control in the component class, you must associate it with a form control element in the template. Update the template with the form control using the formControl binding provided by FormControlDirective, which is also included in the ReactiveFormsModule.

<label> Name: <input type="text" [formControl]="name"> </label>
src/app/name-editor/name-editor.component.html
      
      <label>
  Name:
  <input type="text" [formControl]="name">
</label>
    

使用這種範本繫結語法,把該表單控制元件註冊給了範本中名為 name 的輸入元素。這樣,表單控制元件和 DOM 元素就可以互相通訊了:檢視會反映模型的變化,模型也會反映檢視中的變化。

Using the template binding syntax, the form control is now registered to the name input element in the template. The form control and DOM element communicate with each other: the view reflects changes in the model, and the model reflects changes in the view.

顯示該元件

Display the component

把該元件新增到範本時,將顯示指派給 name 的表單控制元件。

The form control assigned to name is displayed when the component is added to a template.

<app-name-editor></app-name-editor>
src/app/app.component.html (name editor)
      
      <app-name-editor></app-name-editor>
    

顯示表單控制元件的值

Displaying a form control value

你可以用下列方式顯示它的值:

You can display the value in the following ways.

  • 透過可觀察物件 valueChanges,你可以在範本中使用 AsyncPipe 或在元件類別中使用 subscribe() 方法來監聽表單值的變化。

    Through the valueChanges observable where you can listen for changes in the form's value in the template using AsyncPipe or in the component class using the subscribe() method.

  • 使用 value 屬性。它能讓你獲得當前值的一份快照。

    With the value property, which gives you a snapshot of the current value.

下面的例子展示了如何在範本中使用插值顯示當前值。

The following example shows you how to display the current value using interpolation in the template.

<p> Value: {{ name.value }} </p>
src/app/name-editor/name-editor.component.html (control value)
      
      <p>
  Value: {{ name.value }}
</p>
    

一旦你修改了表單控制元件所關聯的元素,這裡顯示的值也跟著變化了。

The displayed value changes as you update the form control element.

響應式表單還能透過每個實例的屬性和方法提供關於特定控制元件的更多資訊。AbstractControl 的這些屬性和方法用於控制表單狀態,並在處理表單校驗時決定何時顯示資訊。 欲知詳情,參閱稍後的輸入驗證一節。

Reactive forms provide access to information about a given control through properties and methods provided with each instance. These properties and methods of the underlying AbstractControl class are used to control form state and determine when to display messages when handling input validation.

要了解 FormControl 的其它屬性和方法,參閱 API 參考手冊

Read about other FormControl properties and methods in the API Reference.

替換表單控制元件的值

Replacing a form control value

響應式表單還有一些方法可以用程式設計的方式修改控制元件的值,它讓你可以靈活的修改控制元件的值而不需要藉助使用者互動。FormControl 提供了一個 setValue() 方法,它會修改這個表單控制元件的值,並且驗證與控制元件結構相對應的值的結構。比如,當從後端 API 或服務接收到了表單資料時,可以透過 setValue() 方法來把原來的值替換為新的值。

Reactive forms have methods to change a control's value programmatically, which gives you the flexibility to update the value without user interaction. A form control instance provides a setValue() method that updates the value of the form control and validates the structure of the value provided against the control's structure. For example, when retrieving form data from a backend API or service, use the setValue() method to update the control to its new value, replacing the old value entirely.

下列的例子往元件類別中添加了一個方法,它使用 setValue() 方法來修改 Nancy 控制元件的值。

The following example adds a method to the component class to update the value of the control to Nancy using the setValue() method.

updateName() { this.name.setValue('Nancy'); }
src/app/name-editor/name-editor.component.ts (update value)
      
      updateName() {
  this.name.setValue('Nancy');
}
    

修改範本,新增一個按鈕,用於模擬改名操作。在點 Update Name 按鈕之前表單控制元件元素中輸入的任何值都會回顯為它的當前值。

Update the template with a button to simulate a name update. When you click the Update Name button, the value entered in the form control element is reflected as its current value.

<p> <button (click)="updateName()">Update Name</button> </p>
src/app/name-editor/name-editor.component.html (update value)
      
      <p>
  <button (click)="updateName()">Update Name</button>
</p>
    

由於表單模型是該控制元件的事實之源,因此當你單擊該按鈕時,元件中該輸入框的值也變化了,覆蓋掉它的當前值。

The form model is the source of truth for the control, so when you click the button, the value of the input is changed within the component class, overriding its current value.

注意:在這個例子中,你只使用單個控制元件,但是當呼叫 FormGroupFormArray實例的 setValue() 方法時,傳入的值就必須匹配控制元件組或控制元件陣列的結構才行。

Note: In this example, you're using a single control. When using the setValue() method with a form group or form array instance, the value needs to match the structure of the group or array.

把表單控制元件分組

Grouping form controls

表單中通常會包含幾個相互關聯的控制元件。響應式表單提供了兩種把多個相關控制元件分組到同一個輸入表單中的方法。

Forms typically contain several related controls. Reactive forms provide two ways of grouping multiple related controls into a single input form.

  • 表單定義了一個帶有一組控制元件的表單,你可以把它們放在一起管理。表單組的基礎知識將在本節中討論。你也可以透過巢狀表單組來建立更復雜的表單。

    A form group defines a form with a fixed set of controls that you can manage together. Form group basics are discussed in this section. You can also nest form groups to create more complex forms.

  • 表單陣列定義了一個動態表單,你可以在執行時新增和刪除控制元件。你也可以透過巢狀表單陣列來建立更復雜的表單。欲知詳情,參閱下面的建立動態表單

    A form array defines a dynamic form, where you can add and remove controls at run time. You can also nest form arrays to create more complex forms. For more about this option, see Creating dynamic forms below.

就像 FormControl 的實例能讓你控制單個輸入框所對應的控制元件一樣,FormGroup 的實例也能追蹤一組 FormControl 實例(比如一個表單)的表單狀態。當建立 FormGroup 時,其中的每個控制元件都會根據其名字進行追蹤。下面的例子展示了如何管理單個控制元件組中的多個 FormControl 實例。

Just as a form control instance gives you control over a single input field, a form group instance tracks the form state of a group of form control instances (for example, a form). Each control in a form group instance is tracked by name when creating the form group. The following example shows how to manage multiple form control instances in a single group.

產生一個 ProfileEditor 元件並從 @angular/forms 套件中匯入 FormGroupFormControl 類別。

Generate a ProfileEditor component and import the FormGroup and FormControl classes from the @angular/forms package.

ng generate component ProfileEditor
      
      ng generate component ProfileEditor
    
import { FormGroup, FormControl } from '@angular/forms';
src/app/profile-editor/profile-editor.component.ts (imports)
      
      import { FormGroup, FormControl } from '@angular/forms';
    

要將表單組新增到此元件中,請執行以下步驟。

To add a form group to this component, take the following steps.

  1. 建立一個 FormGroup 實例。

    Create a FormGroup instance.

  2. 把這個 FormGroup 模型關聯到檢視。

    Associate the FormGroup model and view.

  3. 儲存表單資料。

    Save the form data.

建立一個 FormGroup 實例

Create a FormGroup instance

在元件類別中建立一個名叫 profileForm 的屬性,並設定為 FormGroup 的一個新實例。要初始化這個 FormGroup,請為建構函式提供一個由控制元件組成的物件,物件中的每個名字都要和表單控制元件的名字一一對應。

Create a property in the component class named profileForm and set the property to a new form group instance. To initialize the form group, provide the constructor with an object of named keys mapped to their control.

對此個人檔案表單,要新增兩個 FormControl 實例,名字分別為 firstNamelastName

For the profile form, add two form control instances with the names firstName and lastName.

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(''), }); }
src/app/profile-editor/profile-editor.component.ts (form 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(''),
  });
}
    

現在,這些獨立的表單控制元件被收集到了一個控制元件組中。這個 FormGroup 用物件的形式提供了它的模型值,這個值來自組中每個控制元件的值。 FormGroup 實例擁有和 FormControl 實例相同的屬性(比如 valueuntouched)和方法(比如 setValue())。

The individual form controls are now collected within a group. A FormGroup instance provides its model value as an object reduced from the values of each control in the group. A form group instance has the same properties (such as value and untouched) and methods (such as setValue()) as a form control instance.

把這個 FormGroup 模型關聯到檢視。

Associate the FormGroup model and view

這個表單組還能追蹤其中每個控制元件的狀態及其變化,所以如果其中的某個控制元件的狀態或值變化了,父控制元件也會發出一次新的狀態變更或值變更事件。該控制元件組的模型來自它的所有成員。在定義了這個模型之後,你必須更新範本,來把該模型反映到檢視中。

A form group tracks the status and changes for each of its controls, so if one of the controls changes, the parent control also emits a new status or value change. The model for the group is maintained from its members. After you define the model, you must update the template to reflect the model in the view.

<form [formGroup]="profileForm"> <label> First Name: <input type="text" formControlName="firstName"> </label> <label> Last Name: <input type="text" formControlName="lastName"> </label> </form>
src/app/profile-editor/profile-editor.component.html (template form group)
      
      <form [formGroup]="profileForm">
  
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>

  <label>
    Last Name:
    <input type="text" formControlName="lastName">
  </label>

</form>
    

注意,就像 FormGroup 所包含的那控制元件一樣,profileForm 這個 FormGroup 也透過 FormGroup 指令繫結到了 form 元素,在該模型和表單中的輸入框之間建立了一個通訊層。 由 FormControlName 指令提供的 formControlName 屬性把每個輸入框和 FormGroup 中定義的表單控制元件繫結起來。這些表單控制元件會和相應的元素通訊,它們還把更改傳給 FormGroup,這個 FormGroup 是模型值的事實之源。

Note that just as a form group contains a group of controls, the profileForm FormGroup is bound to the form element with the FormGroup directive, creating a communication layer between the model and the form containing the inputs. The formControlName input provided by the FormControlName directive binds each individual input to the form control defined in FormGroup. The form controls communicate with their respective elements. They also communicate changes to the form group instance, which provides the source of truth for the model value.

儲存表單資料

Save form data

ProfileEditor 元件從使用者那裡獲得輸入,但在真實的場景中,你可能想要先捕獲表單的值,等將來在元件外部進行處理。 FormGroup 指令會監聽 form 元素髮出的 submit 事件,併發出一個 ngSubmit 事件,讓你可以繫結一個回呼(Callback)函式。

The ProfileEditor component accepts input from the user, but in a real scenario you want to capture the form value and make available for further processing outside the component. The FormGroup directive listens for the submit event emitted by the form element and emits an ngSubmit event that you can bind to a callback function.

onSubmit() 回呼(Callback)方法新增為 form 標籤上的 ngSubmit 事件監聽器。

Add an ngSubmit event listener to the form tag with the onSubmit() callback method.

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
src/app/profile-editor/profile-editor.component.html (submit event)
      
      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
    

ProfileEditor 元件上的 onSubmit() 方法會捕獲 profileForm 的當前值。要保持該表單的封裝性,就要使用 EventEmitter 向元件外部提供該表單的值。下面的例子會使用 console.warn 把這個值記錄到瀏覽器的控制檯中。

The onSubmit() method in the ProfileEditor component captures the current value of profileForm. Use EventEmitter to keep the form encapsulated and to provide the form value outside the component. The following example uses console.warn to log a message to the browser console.

onSubmit() { // TODO: Use EventEmitter with form value console.warn(this.profileForm.value); }
src/app/profile-editor/profile-editor.component.ts (submit method)
      
      onSubmit() {
  // TODO: Use EventEmitter with form value
  console.warn(this.profileForm.value);
}
    

form 標籤所發出的 submit 事件是原生 DOM 事件,透過點選型別為 submit 的按鈕可以觸發本事件。這還讓使用者可以用Enter鍵來提交填完的表單。

The submit event is emitted by the form tag using the native DOM event. You trigger the event by clicking a button with submit type. This allows the user to press the Enter key to submit the completed form.

往表單的底部新增一個 button,用於觸發表單提交。

Use a button element to add a button to the bottom of the form to trigger the form submission.

<button type="submit" [disabled]="!profileForm.valid">Submit</button>
src/app/profile-editor/profile-editor.component.html (submit button)
      
      <button type="submit" [disabled]="!profileForm.valid">Submit</button>
    

注意:上面這個程式碼片段中的按鈕還附加了一個 disabled 繫結,用於在 profileForm 無效時禁用該按鈕。目前你還沒有執行任何表單驗證邏輯,因此該按鈕始終是可用的。稍後的驗證表單輸入部分會講解基礎的表單驗證。

Note: The button in the snippet above also has a disabled binding attached to it to disable the button when profileForm is invalid. You aren't performing any validation yet, so the button is always enabled. Basic form validation is covered in the Validating form input section.

顯示元件

Display the component

要顯示包含此表單的 ProfileEditor 元件,請把它新增到元件範本中。

To display the ProfileEditor component that contains the form, add it to a component template.

<app-profile-editor></app-profile-editor>
src/app/app.component.html (profile editor)
      
      <app-profile-editor></app-profile-editor>
    

ProfileEditor 讓你能管理 FormGroup 中的 firstNamelastNameFormControl 實例。

ProfileEditor allows you to manage the form control instances for the firstName and lastName controls within the form group instance.

建立巢狀的表單組

Creating nested form groups

表單組可以同時接受單個表單控制元件實例和其它表單組實例作為其子控制元件。這可以讓複雜的表單模型更容易維護,並在邏輯上把它們分組到一起。

Form groups can accept both individual form control instances and other form group instances as children. This makes composing complex form models easier to maintain and logically group together.

如果要建構複雜的表單,如果能在更小的分區中管理不同類別的資訊就會更容易一些。使用巢狀的 FormGroup 可以讓你把大型表單組織成一些稍小的、易管理的分組。

When building complex forms, managing the different areas of information is easier in smaller sections. Using a nested form group instance allows you to break large forms groups into smaller, more manageable ones.

要製作更復雜的表單,請遵循如下步驟。

To make more complex forms, use the following steps.

  1. 建立一個巢狀的表單組。

    Create a nested group.

  2. 在範本中對這個巢狀表單分組。

    Group the nested form in the template.

某些型別的資訊天然就屬於同一個組。比如名稱和地址就是這類別巢狀組的典型例子,下面的例子中就用到了它們。

Some types of information naturally fall into the same group. A name and address are typical examples of such nested groups, and are used in the following examples.

建立一個巢狀組

Create a nested group

要在 profileForm 中建立一個巢狀組,就要把一個巢狀的 address 元素新增到此表單組的實例中。

To create a nested group in profileForm, add a nested address element to the form group instance.

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('') }) }); }
src/app/profile-editor/profile-editor.component.ts (nested form 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('')
    })
  });
}
    

在這個例子中,address group 把現有的 firstNamelastName 控制元件和新的 streetcitystatezip 控制元件組合在一起。雖然 address 這個 FormGroupprofileForm 這個整體 FormGroup 的一個子控制元件,但是仍然適用同樣的值和狀態的變更規則。來自內嵌控制元件組的狀態和值的變更將會冒泡到它的父控制元件組,以維護整體模型的一致性。

In this example, address group combines the current firstName and lastName controls with the new street, city, state, and zip controls. Even though the address element in the form group is a child of the overall profileForm element in the form group, the same rules apply with value and status changes. Changes in status and value from the nested form group propagate to the parent form group, maintaining consistency with the overall model.

在範本中對此巢狀表單分組

Group the nested form in the template

在修改了元件類別中的模型之後,還要修改範本,來把這個 FormGroup 實例對接到它的輸入元素。

After you update the model in the component class, update the template to connect the form group instance and its input elements.

把包含 streetcitystatezip 欄位的 address 表單組新增到 ProfileEditor 範本中。

Add the address form group containing the street, city, state, and zip fields to the ProfileEditor template.

<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>
src/app/profile-editor/profile-editor.component.html (template nested form group)
      
      <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>
    

ProfileEditor 表單顯示為一個組,但是將來這個模型會被進一步細分,以表示邏輯分組區域。

The ProfileEditor form is displayed as one group, but the model is broken down further to represent the logical grouping areas.

提示:這裡使用了 value 屬性和 JsonPipe 管道在元件範本中顯示了這個 FormGroup 的值。

Tip Display the value for the form group instance in the component template using the value property and JsonPipe.

更新部分資料模型

Updating parts of the data model

當修改包含多個 FormGroup 實例的值時,你可能只希望更新模型中的一部分,而不是完全替換掉。這一節會講解該如何更新 AbstractControl 模型中的一部分。

When updating the value for a form group instance that contains multiple controls, you may only want to update parts of the model. This section covers how to update specific parts of a form control data model.

有兩種更新模型值的方式:

There are two ways to update the model value:

  • 使用 setValue() 方法來為單個控制元件設定新值。 setValue() 方法會嚴格遵循表單組的結構,並整體性替換控制元件的值。

    Use the setValue() method to set a new value for an individual control. The setValue() method strictly adheres to the structure of the form group and replaces the entire value for the control.

  • 使用 patchValue() 方法可以用物件中所定義的任何屬性為表單模型進行替換。

    Use the patchValue() method to replace any properties defined in the object that have changed in the form model.

setValue() 方法的嚴格檢查可以幫助你捕獲複雜表單巢狀中的錯誤,而 patchValue() 在遇到那些錯誤時可能會默默的失敗。

The strict checks of the setValue() method help catch nesting errors in complex forms, while patchValue() fails silently on those errors.

ProfileEditorComponent 中,使用 updateProfile 方法傳入下列資料可以更新使用者的名字與街道住址。

In ProfileEditorComponent, use the updateProfile method with the example below to update the first name and street address for the user.

updateProfile() { this.profileForm.patchValue({ firstName: 'Nancy', address: { street: '123 Drew Street' } }); }
src/app/profile-editor/profile-editor.component.ts (patch value)
      
      updateProfile() {
  this.profileForm.patchValue({
    firstName: 'Nancy',
    address: {
      street: '123 Drew Street'
    }
  });
}
    

透過往範本中新增一個按鈕來模擬一次更新操作,以修改使用者檔案。

Simulate an update by adding a button to the template to update the user profile on demand.

<p> <button (click)="updateProfile()">Update Profile</button> </p>
src/app/profile-editor/profile-editor.component.html (update value)
      
      <p>
  <button (click)="updateProfile()">Update Profile</button>
</p>
    

當點選按鈕時,profileForm 模型中只有 firstNamestreet 被修改了。注意,street 是在 address 屬性的物件中被修改的。這種結構是必須的,因為 patchValue() 方法要針對模型的結構進行更新。patchValue() 只會更新表單模型中所定義的那些屬性。

When a user clicks the button, the profileForm model is updated with new values for firstName and street. Notice that street is provided in an object inside the address property. This is necessary because the patchValue() method applies the update against the model structure. PatchValue() only updates properties that the form model defines.

使用 FormBuilder 服務產生控制元件

Using the FormBuilder service to generate controls

當需要與多個表單打交道時,手動建立多個表單控制元件實例會非常繁瑣。FormBuilder 服務提供了一些便捷方法來產生表單控制元件。FormBuilder 在幕後也使用同樣的方式來建立和返回這些實例,只是用起來更簡單。

Creating form control instances manually can become repetitive when dealing with multiple forms. The FormBuilder service provides convenient methods for generating controls.

透過下列步驟可以利用這項服務。

Use the following steps to take advantage of this service.

  1. 匯入 FormBuilder 類別。

    Import the FormBuilder class.

  2. 注入這個 FormBuilder 服務。

    Inject the FormBuilder service.

  3. 產生表單內容。

    Generate the form contents.

下面的例子展示了如何重構 ProfileEditor 元件,用 FormBuilder 來代替手工建立這些 FormControlFormGroup 實例。

The following examples show how to refactor the ProfileEditor component to use the form builder service to create form control and form group instances.

匯入 FormBuilder 類別

Import the FormBuilder class

@angular/forms 套件中匯入 FormBuilder 類別。

Import the FormBuilder class from the @angular/forms package.

import { FormBuilder } from '@angular/forms';
src/app/profile-editor/profile-editor.component.ts (import)
      
      import { FormBuilder } from '@angular/forms';
    

注入 FormBuilder 服務

Inject the FormBuilder service

FormBuilder 是一個可注入的服務提供者,它是由 ReactiveFormModule 提供的。只要把它新增到元件的建構函式中就可以注入這個依賴。

The FormBuilder service is an injectable provider that is provided with the reactive forms module. Inject this dependency by adding it to the component constructor.

constructor(private fb: FormBuilder) { }
src/app/profile-editor/profile-editor.component.ts (constructor)
      
      constructor(private fb: FormBuilder) { }
    

產生表單控制元件

Generate form controls

FormBuilder 服務有三個方法:control()group()array()。這些方法都是工廠方法,用於在元件類別中分別產生 FormControlFormGroupFormArray

The FormBuilder service has three methods: control(), group(), and array(). These are factory methods for generating instances in your component classes including form controls, form groups, and form arrays.

group 方法來建立 profileForm 控制元件。

Use the group method to create the profileForm controls.

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) { } }
src/app/profile-editor/profile-editor.component.ts (form builder)
      
      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) { }
}
    

在上面的例子中,你可以使用 group() 方法,用和前面一樣的名字來定義這些屬性。這裡,每個控制元件名對應的值都是一個數組,這個陣列中的第一項是其初始值。

In the example above, you use the group() method with the same object to define the properties in the model. The value for each control name is an array containing the initial value as the first item in the array.

提示:你可以只使用初始值來定義控制元件,但是如果你的控制元件還需要同步或非同步驗證器,那就在這個陣列中的第二項和第三項提供同步和非同步驗證器。

Tip You can define the control with just the initial value, but if your controls need sync or async validation, add sync and async validators as the second and third items in the array.

比較一下用表單建構器和手動建立實例這兩種方式。

Compare using the form builder to creating the instances manually.

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 = this.fb.group({ firstName: [''], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), });
      
      profileForm = new FormGroup({
  firstName: new FormControl(''),
  lastName: new FormControl(''),
  address: new FormGroup({
    street: new FormControl(''),
    city: new FormControl(''),
    state: new FormControl(''),
    zip: new FormControl('')
  })
});
    

驗證表單輸入

Validating form input

表單驗證用於確保使用者的輸入是完整和正確的。本節講解了如何把單個驗證器新增到表單控制元件中,以及如何顯示表單的整體狀態。表單驗證的更多知識在表單驗證一章中有詳細的講解。

Form validation is used to ensure that user input is complete and correct. This section covers adding a single validator to a form control and displaying the overall form status. Form validation is covered more extensively in the Form Validation guide.

使用下列步驟新增表單驗證。

Use the following steps to add form validation.

  1. 在表單元件中匯入一個驗證器函式。

    Import a validator function in your form component.

  2. 把這個驗證器新增到表單中的相應欄位。

    Add the validator to the field in the form.

  3. 新增邏輯來處理驗證狀態。

    Add logic to handle the validation status.

最常見的驗證是做一個必填欄位。下面的例子給出瞭如何在 firstName 控制元件中新增必填驗證並顯示驗證結果的方法。

The most common validation is making a field required. The following example shows how to add a required validation to the firstName control and display the result of validation.

匯入驗證器函式

Import a validator function

響應式表單包含了一組開箱即用的常用驗證器函式。這些函式接收一個控制元件,用以驗證並根據驗證結果返回一個錯誤物件或空值。

Reactive forms include a set of validator functions for common use cases. These functions receive a control to validate against and return an error object or a null value based on the validation check.

@angular/forms 套件中匯入 Validators 類別。

Import the Validators class from the @angular/forms package.

import { Validators } from '@angular/forms';
src/app/profile-editor/profile-editor.component.ts (import)
      
      import { Validators } from '@angular/forms';
    

建一個必填欄位

Make a field required

ProfileEditor 元件中,把靜態方法 Validators.required 設定為 firstName 控制元件值陣列中的第二項。

In the ProfileEditor component, add the Validators.required static method as the second item in the array for the firstName control.

profileForm = this.fb.group({ firstName: ['', Validators.required], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), });
src/app/profile-editor/profile-editor.component.ts (required validator)
      
      profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
});
    

HTML5 有一組內建的屬性,用來進行原生驗證,包括 requiredminlengthmaxlength 等。雖然是可選的,不過你也可以在表單的輸入元素上把它們新增為附加屬性來使用它們。這裡我們把 required 屬性新增到 firstName 輸入元素上。

HTML5 has a set of built-in attributes that you can use for native validation, including required, minlength, and maxlength. You can take advantage of these optional attributes on your form input elements. Add the required attribute to the firstName input element.

<input type="text" formControlName="firstName" required>
src/app/profile-editor/profile-editor.component.html (required attribute)
      
      <input type="text" formControlName="firstName" required>
    

注意:這些 HTML5 驗證器屬性可以和 Angular 響應式表單提供的內建驗證器組合使用。組合使用這兩種驗證器實踐,可以防止在範本檢查完之後表示式再次被修改導致的錯誤。

Caution: Use these HTML5 validation attributes in combination with the built-in validators provided by Angular's reactive forms. Using these in combination prevents errors when the expression is changed after the template has been checked.

顯示表單狀態

Display form status

當你往表單控制元件上添加了一個必填欄位時,它的初始值是無效的(invalid)。這種無效狀態會傳播到其父 FormGroup 元素中,也讓這個 FormGroup 的狀態變為無效的。你可以透過該 FormGroup 實例的 status 屬性來訪問其當前狀態。

When you add a required field to the form control, its initial status is invalid. This invalid status propagates to the parent form group element, making its status invalid. Access the current status of the form group instance through its status property.

使用插值顯示 profileForm 的當前狀態。

Display the current status of profileForm using interpolation.

<p> Form Status: {{ profileForm.status }} </p>
src/app/profile-editor/profile-editor.component.html (display status)
      
      <p>
  Form Status: {{ profileForm.status }}
</p>
    

提交按鈕被禁用了,因為 firstName 控制元件的必填項規則導致了 profileForm 也是無效的。在你填寫了 firstName 輸入框之後,該表單就變成了有效的,並且提交按鈕也啟用了。

The Submit button is disabled because profileForm is invalid due to the required firstName form control. After you fill out the firstName input, the form becomes valid and the Submit button is enabled.

要了解表單驗證的更多知識,參閱表單驗證指南。

For more on form validation, visit the Form Validation guide.

建立動態表單

Creating dynamic forms

FormArrayFormGroup 之外的另一個選擇,用於管理任意數量的匿名控制元件。像 FormGroup 實例一樣,你也可以往 FormArray 中動態插入和移除控制元件,並且 FormArray 實例的值和驗證狀態也是根據它的子控制元件計算得來的。 不過,你不需要為每個控制元件定義一個名字作為 key,因此,如果你事先不知道子控制元件的數量,這就是一個很好的選擇。

FormArray is an alternative to FormGroup for managing any number of unnamed controls. As with form group instances, you can dynamically insert and remove controls from form array instances, and the form array instance value and validation status is calculated from its child controls. However, you don't need to define a key for each control by name, so this is a great option if you don't know the number of child values in advance.

要定義一個動態表單,請執行以下步驟。

To define a dynamic form, take the following steps.

  1. 匯入 FormArray 類別。

    Import the FormArray class.

  2. 定義一個 FormArray 控制元件。

    Define a FormArray control.

  3. 使用 getter 方法訪問 FormArray 控制元件。

    Access the FormArray control with a getter method.

  4. 在範本中顯示這個表單陣列。

    Display the form array in a template.

下面的例子展示了如何在 ProfileEditor 中管理別名陣列。

The following example shows you how to manage an array of aliases in ProfileEditor.

匯入 FormArray 類別

Import the FormArray class

@angular/form 中匯入 FormArray,以使用它的型別資訊。FormBuilder 服務用於建立 FormArray 實例。

Import the FormArray class from @angular/forms to use for type information. The FormBuilder service is ready to create a FormArray instance.

import { FormArray } from '@angular/forms';
src/app/profile-editor/profile-editor.component.ts (import)
      
      import { FormArray } from '@angular/forms';
    

定義 FormArray 控制元件

Define a FormArray control

你可以透過把一組(從零項到多項)控制元件定義在一個數組中來初始化一個 FormArray。為 profileForm 新增一個 aliases 屬性,把它定義為 FormArray 型別。

You can initialize a form array with any number of controls, from zero to many, by defining them in an array. Add an aliases property to the form group instance for profileForm to define the form array.

使用 FormBuilder.array() 方法來定義該陣列,並用 FormBuilder.control() 方法來往該陣列中新增一個初始控制元件。

Use the FormBuilder.array() method to define the array, and the FormBuilder.control() method to populate the array with an initial control.

profileForm = this.fb.group({ firstName: ['', Validators.required], lastName: [''], address: this.fb.group({ street: [''], city: [''], state: [''], zip: [''] }), aliases: this.fb.array([ this.fb.control('') ]) });
src/app/profile-editor/profile-editor.component.ts (aliases form array)
      
      profileForm = this.fb.group({
  firstName: ['', Validators.required],
  lastName: [''],
  address: this.fb.group({
    street: [''],
    city: [''],
    state: [''],
    zip: ['']
  }),
  aliases: this.fb.array([
    this.fb.control('')
  ])
});
    

FormGroup 中的這個 aliases 控制元件現在管理著一個控制元件,將來還可以動態新增多個。

The aliases control in the form group instance is now populated with a single control until more controls are added dynamically.

訪問 FormArray 控制元件

Access the FormArray control

相對於重複使用 profileForm.get() 方法獲取每個實例的方式,getter 可以讓你輕鬆訪問表單陣列各個實例中的別名。 表單陣列實例用一個數組來代表未定數量的控制元件。透過 getter 來訪問控制元件很方便,這種方法還能很容易地重複處理更多控制元件。

A getter provides easy access to the aliases in the form array instance compared to repeating the profileForm.get() method to get each instance. The form array instance represents an undefined number of controls in an array. It's convenient to access a control through a getter, and this approach is easy to repeat for additional controls.

使用 getter 語法建立類別屬性 aliases,以從父表單組中接收表示綽號的表單陣列控制元件。

Use the getter syntax to create an aliases class property to retrieve the alias's form array control from the parent form group.

get aliases() { return this.profileForm.get('aliases') as FormArray; }
src/app/profile-editor/profile-editor.component.ts (aliases getter)
      
      get aliases() {
  return this.profileForm.get('aliases') as FormArray;
}
    

注意:因為返回的控制元件的型別是 AbstractControl,所以你要為該方法提供一個顯式的型別宣告來訪問 FormArray 特有的語法。

Note: Because the returned control is of the type AbstractControl, you need to provide an explicit type to access the method syntax for the form array instance.

定義一個方法來把一個綽號控制元件動態插入到綽號 FormArray 中。用 FormArray.push() 方法把該控制元件新增為陣列中的新條目。

Define a method to dynamically insert an alias control into the alias's form array. The FormArray.push() method inserts the control as a new item in the array.

addAlias() { this.aliases.push(this.fb.control('')); }
src/app/profile-editor/profile-editor.component.ts (add alias)
      
      addAlias() {
  this.aliases.push(this.fb.control(''));
}
    

在這個範本中,這些控制元件會被迭代,把每個控制元件都顯示為一個獨立的輸入框。

In the template, each control is displayed as a separate input field.

在範本中顯示表單陣列

Display the form array in the template

要想為表單模型新增 aliases,你必須把它加入到範本中供使用者輸入。和 FormGroupNameDirective 提供的 formGroupName 一樣,FormArrayNameDirective 也使用 formArrayName 在這個 FormArray 實例和範本之間建立繫結。

To attach the aliases from your form model, you must add it to the template. Similar to the formGroupName input provided by FormGroupNameDirective, formArrayName binds communication from the form array instance to the template with FormArrayNameDirective.

formGroupName <div> 元素的結束標籤下方,新增一段範本 HTML。

Add the template HTML below after the <div> closing the formGroupName element.

<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>
src/app/profile-editor/profile-editor.component.html (aliases form array 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 FormArray 提供的每個 FormControl 進行迭代。因為 FormArray 中的元素是匿名的,所以你要把索引號賦值給 i 變數,並且把它傳給每個控制元件的 formControlName 輸入屬性。

The *ngFor directive iterates over each form control instance provided by the aliases form array instance. Because form array elements are unnamed, you assign the index to the i variable and pass it to each control to bind it to the formControlName input.

每當新的 alias 加進來時,FormArray 的實例就會基於這個索引號提供它的控制元件。這將允許你在每次計算根控制元件的狀態和值時追蹤每個控制元件。

Each time a new alias instance is added, the new form array instance is provided its control based on the index. This allows you to track each individual control when calculating the status and value of the root control.

新增一個別名

Add an alias

最初,表單只包含一個綽號欄位,點選 Add Alias 按鈕,就出現了另一個欄位。你還可以驗證由範本底部的“Form Value”顯示出來的表單模型所報告的這個綽號陣列。

Initially, the form contains one Alias field. To add another field, click the Add Alias button. You can also validate the array of aliases reported by the form model displayed by Form Value at the bottom of the template.

注意:除了為每個綽號使用 FormControl 之外,你還可以改用 FormGroup 來組合上一些額外欄位。對其中的每個條目定義控制元件的過程和前面沒有區別。

Note: Instead of a form control instance for each alias, you can compose another form group instance with additional fields. The process of defining a control for each item is the same.

響應式表單 API 彙總

Reactive forms API summary

下表給出了用於建立和管理響應式表單控制元件的基礎類別和服務。要了解完整的語法,請參閱 API 文件中的 Forms 套件

The following table lists the base classes and services used to create and manage reactive form controls. For complete syntax details, see the API reference documentation for the Forms package.

類別

Classes

類別

Class

說明

Description

AbstractControl

所有三種表單控制元件類別(FormControlFormGroupFormArray)的抽象基底類別。它提供了一些公共的行為和屬性。

The abstract base class for the concrete form control classes FormControl, FormGroup, and FormArray. It provides their common behaviors and properties.

FormControl

管理單體表單控制元件的值和有效性狀態。它對應於 HTML 的表單控制元件,比如 <input><select>

Manages the value and validity status of an individual form control. It corresponds to an HTML form control such as <input> or <select>.

FormGroup

管理一組 AbstractControl 實例的值和有效性狀態。該組的屬性中包括了它的子控制元件。元件中的最上層表單就是 FormGroup

Manages the value and validity state of a group of AbstractControl instances. The group's properties include its child controls. The top-level form in your component is FormGroup.

FormArray

管理一些 AbstractControl 實例陣列的值和有效性狀態。

Manages the value and validity state of a numerically indexed array of AbstractControl instances.

FormBuilder

一個可注入的服務,提供一些用於提供建立控制元件實例的工廠方法。

An injectable service that provides factory methods for creating control instances.

指令

Directives

指令

Directive

說明

Description

FormControlDirective

把一個獨立的 FormControl 實例繫結到表單控制元件元素。

Syncs a standalone FormControl instance to a form control element.

FormControlName

把一個現有 FormGroup 中的 FormControl 實例根據名字繫結到表單控制元件元素。

Syncs FormControl in an existing FormGroup instance to a form control element by name.

FormGroupDirective

把一個現有的 FormGroup 實例繫結到 DOM 元素。

Syncs an existing FormGroup instance to a DOM element.

FormGroupName

把一個內嵌的 FormGroup 實例繫結到一個 DOM 元素。

Syncs a nested FormGroup instance to a DOM element.

FormArrayName

把一個內嵌的 FormArray 實例繫結到一個 DOM 元素。

Syncs a nested FormArray instance to a DOM element.