[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


<<:  鼠年全马铁人挑战 WEEK 40:最终章

>>:  让WooCommerce的订单通知信里面的商品名称附带商品网址的程序码

day6 初级系统工程师 (雷)管理眼花撩乱的机房,不是幸福

来部落格看图文并茂文章 补觉鸣诗 时间回到我入行第二年 这时才算是正式的系统工程师并开机接触机房 最...

Day4 - 2D渲染环境基础篇 I - 成为Canvas Ninja ~ 理解2D渲染的精髓

进入2D渲染的世界 我们在前面的章节有提到,任何Canvas的相关程序,起手式必定是先取得渲染环境,...

如何建立 Windows USB 安装随身碟-适用 Win 10, Win Server 2019

使用 USB 跟传统 DVD 一样可以正常安装 Windows,而且携带方便,还不需要准备光碟机。 ...

乔叔教 Elastic - 30 - Elasticsearch 的优化技巧 (4/4) - Shard 的最佳化管理

Elasticsearch 的优化技巧 系列文章索引 (1/4) - Indexing 索引效能优化...

Day 21 :广度优先搜寻 Breadth-First search(BFS)

说到广度优先搜寻我一定要现知道Queue Queue(伫列)是先进来的元素先出去(First In ...