深入瞭解angular中的表單(響應式和模板驅動)

2022-05-14 01:46:41
本篇文章帶大家瞭解一下中的表單,聊聊響應式表單與模板驅動表單,介紹一下響應式表單怎麼驗證表單輸入,希望對大家有所幫助!

一、angular表單簡介

Angular 提供了兩種不同的方法來通過表單處理使用者輸入:響應式表單模板驅動表單。 兩者都從檢視中捕獲使用者輸入事件、驗證使用者輸入、建立表單模型、修改資料模型,並提供跟蹤這些更改的途徑。【相關教學推薦:《》】

1.1 響應式表單與模板驅動表單的差異

  • 響應式表單提供對底層表單物件模型直接顯式的存取。它們與模板驅動表單相比,更加健壯:它們的可延伸性、可複用性和可測試性都更高。如果表單是你的應用程式的關鍵部分,或者你已經在使用響應式表單來構建應用,那就使用響應式表單。
  • 模板驅動表單依賴模板中的指令來建立和操作底層的物件模型。它們對於嚮應用新增一個簡單的表單非常有用,比如電子郵寄清單登入檔單。它們很容易新增到應用中,但在擴充套件性方面不如響應式表單。如果你有可以只在模板中管理的非常基本的表單需求和邏輯,那麼模板驅動表單就很合適。

響應式模板驅動
建立表單模型顯式的,在元件類中建立隱式的,由指令建立
資料模型結構化和不可變的非結構化和可變的
可預測性同步非同步
表單驗證函數指令

1.2 建立表單模型

響應式表單和模板驅動型表單都會跟蹤使用者與之互動的表單輸入元素和元件模型中的表單資料之間的值變更。這兩種方法共用同一套底層構建塊,只在如何建立管理常用表單控制元件範例方面有所不同。

1.3 常用表單基礎類

響應式表單和模板驅動表單都建立在下列基礎類之上。

  • FormControl 範例用於追蹤單個表單控制元件的值和驗證狀態。
  • FormGroup 用於追蹤一個表單控制元件組的值和狀態。
  • FormArray 用於追蹤表單控制元件陣列的值和狀態。
  • ControlValueAccessor 用於在 Angular 的 FormControl 範例和原生 DOM 元素之間建立一個橋樑。

二、 響應式表單

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

2.1 新增基礎表單控制元件

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

  • 在你的應用中註冊響應式表單模組。該模組宣告了一些你要用在響應式表單中的指令。

  • 生成一個新的 FormControl 範例,並把它儲存在元件中。

  • 在模板中註冊這個 FormControl。

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

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

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

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

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 的建構函式設定初始值,這個例子中它是空字串。通過在你的元件類中建立這些控制元件,你可以直接對錶單控制元件的狀態進行監聽修改校驗

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

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

2.2 顯示錶單控制元件的值

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

  • 通過可觀察物件 valueChanges,你可以在模板中使用 AsyncPipe 或在元件類中使用 subscribe() 方法來監聽表單值的變化。
  • 使用 value 屬性。它能讓你獲得當前值的一份快照。
<label>
  Name:
  <input type="text" [formControl]="name">
</label>
<p>Value: {{ name.value }}</p>
  public name = new FormControl('test');

  public testValueChange() {
    this.name.valueChanges.subscribe({
      next: value => {
        console.log("name value is: " + value);
      }
    })
  }

2.3 替換表單控制元件的值

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

updateName() {
  this.name.setValue('Nancy' + new Date().getTime());
}
<p>
  <button (click)="updateName()">Update Name</button>
</p>

2.4 把表單控制元件分組

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

  • 表單組定義了一個帶有一組控制元件的表單,你可以把它們放在一起管理。表單組的基礎知識將在本節中討論。你也可以通過巢狀表單組來建立更復雜的表單。
  • 表單陣列定義了一個動態表單,你可以在執行時新增和刪除控制元件。你也可以通過巢狀表單陣列來建立更復雜的表單

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

  • 建立一個 FormGroup 範例。

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

  • 儲存表單資料。

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

import { FormControl, FormGroup } from '@angular/forms';
  profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
  });
  // 可以整個獲取值
  public onSubmit() {
    // TODO: Use EventEmitter with form value
    console.warn(this.profileForm.value);// {firstName: "", lastName: ""}
  }
      // 可以藉助 valueChanges 整個可觀察物件整個獲取值
     this.profileForm.valueChanges.subscribe( {
      next: value => {
        console.log("name value is: " + JSON.stringify(value)); // dashboard.component.ts:53 name value is: {"firstName":"dddd","lastName":"bb"}
      }
    })

    // 可以通過後期單個控制元件單獨獲取值
    this.profileForm.get('firstName').valueChanges.subscribe({
      next: value => {
        console.log("First Name is: " + value); // First Name is: aa
      }

ps: 這個 FormGroup 用物件的形式提供了它的模型值,這個值來自組中每個控制元件的值。 FormGroup 範例擁有和 FormControl 範例相同的屬性(比如 value、untouched)和方法(比如 setValue())。

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

<form [formGroup]="profileForm" (ngSubmit)="onSubmit()">
 
  <label>
    First Name:
    <input type="text" formControlName="firstName">
  </label>

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

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

2.5 建立巢狀的表單組

表單組可以同時接受單個表單控制元件範例和其它表單組範例作為其子控制元件。這可以讓複雜的表單模型更容易維護,並在邏輯上把它們分組到一起。
要製作更復雜的表單,請遵循如下步驟。

  • 建立一個巢狀的表單組

  • 板中對這個巢狀表單分組。

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

  public profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    })
  });
    // 可以藉助 valueChanges 整個可觀察物件整個獲取值
    this.profileForm.valueChanges.subscribe( {
      next: value => {
        console.log("name value is: " + JSON.stringify(value));// name value is: {"firstName":"","lastName":"","address":{"street":"b","city":"","state":"","zip":""}}
      }
    });

    // 可以通過後期單個控制元件單獨獲取值
    this.profileForm.get('firstName').valueChanges.subscribe({
      next: value => {
        console.log("First Name is: " + value);
      }
    });

    // 可以獲取form元件某個form組的整個值
    this.profileForm.get('address').valueChanges.subscribe(({
      next: value => {
        console.log('address value is: ' + JSON.stringify(value));// address value is: {"street":"b","city":"","state":"","zip":""}
      }
    }));

    // 可以獲取form元件某個form組的某個formcontrol範例的值
    this.profileForm.get('address').get('street').valueChanges.subscribe(({
      next: value => {
        console.log('street value is: ' + value);// street value is: b
      }
    }));

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

      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

        <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>

2.6 更新部分資料模型

當修改包含多個 FormGroup 範例的值時,你可能只希望更新模型中的一部分,而不是完全替換掉。

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

  • 使用 setValue() 方法來為單個控制元件設定新值。 setValue() 方法會嚴格遵循表單組的結構,並整體性替換控制元件的值
  • 使用 patchValue() 方法可以用物件中所定義的任何屬性為表單模型進行替換。

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

  public updateProfile() {
      // profileForm 模型中只有 firstName 和 street 被修改了。注意,street 是在 address 屬性的物件中被修改的。這種結構是必須的,因為 patchValue() 方法要針對模型的結構進行更新。patchValue() 只會更新表單模型中所定義的那些屬性。
    this.profileForm.patchValue({
      firstName: 'Nancy' + new Date().getTime(),
      address: {
        street: '123 Drew Street' + new Date().getTime()
      }
    });

    // ERROR Error: Must supply a value for form control with name: 'lastName'.
    // setValue() 方法會嚴格遵循表單組的結構
    this.profileForm.setValue({
      firstName: 'Nancy' + new Date().getTime(),
      address: {
        street: '123 Drew Street' + new Date().getTime()
      }
    });
  }

2.7 建立動態表單

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

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

  • 匯入 FormArray 類。

  • 定義一個 FormArray 控制元件。

  • 使用 getter 方法存取 FormArray 控制元件。

  • 在模板中顯示這個表單陣列

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

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

  public profileForm = new FormGroup({
    firstName: new FormControl(''),
    lastName: new FormControl(''),
    address: new FormGroup({
      street: new FormControl(''),
      city: new FormControl(''),
      state: new FormControl(''),
      zip: new FormControl('')
    }),
    aliases: new FormArray([
      new FormControl('1')
    ])
  });
  public aliases = (<FormArray>this.profileForm.get('aliases'));

  public addAlias() {
    (<FormArray>this.profileForm.get('aliases')).push(new FormControl('1'));
  }

      // 獲取整個 formArray 的資料
    this.profileForm.get('aliases').valueChanges.subscribe({
      next: value => {
        console.log('aliases values is: ' + JSON.stringify(value)); // aliases values is: ["1","3"]
      }
    });

    // 獲取 formArray 中單個 formControl 的資料
    (<FormArray>this.profileForm.get('aliases')).controls[0].valueChanges.subscribe({
      next: value => {
        console.log('aliases[0] values is: ' + value); // aliases[0] values is: 0
      }
    })

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

      <form [formGroup]="profileForm" (ngSubmit)="onSubmit()">

        <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>
        <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>
      </form>

2.8 響應式表單 API 彙總

說明
AbstractControl所有三種表單控制元件類(FormControl、FormGroup 和 FormArray)的抽象基礎類別。它提供了一些公共的行為和屬性。
FormControl管理單體表單控制元件的值和有效性狀態。它對應於 HTML 的表單控制元件,比如 或 。
FormGroup管理一組 AbstractControl 範例的值和有效性狀態。該組的屬性中包括了它的子控制元件。元件中的頂層表單就是 FormGroup。
FormArray管理一些 AbstractControl 範例陣列的值和有效性狀態。
FormBuilder一個可注入的服務,提供一些用於提供建立控制元件範例的工廠方法。

三、模板驅動表單

在模板驅動表單中,表單模型是隱式的,而不是顯式的。指令 NgModel 為指定的表單元素建立並管理一個 FormControl 範例。
下面的元件使用模板驅動表單為單個控制元件實現了同樣的輸入欄位。

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

@Component({
  selector: 'app-template-favorite-color',
  template: `
    Favorite Color: <input type="text" [(ngModel)]="favoriteColor">
  `
})
export class FavoriteColorComponent {
  favoriteColor = '';
}

四、響應式表單驗證表單輸入

在元件類中直接把驗證器函數新增到表單控制元件模型上(FormControl)。然後,一旦控制元件發生了變化,Angular 就會呼叫這些函數。

4.1 驗證器(Validator)函數

驗證器函數可以是同步函數,也可以是非同步函數。

  • 同步驗證器:這些同步函數接受一個控制元件範例,然後返回一組驗證錯誤或 null。你可以在範例化一個 FormControl 時把它作為建構函式的第二個引數傳進去。
  • 非同步驗證器 :這些非同步函數接受一個控制元件範例並返回一個 Promise 或 Observable,它稍後會發出一組驗證錯誤或 null。在範例化 FormControl 時,可以把它們作為第三個引數傳入。

出於效能方面的考慮,只有在所有同步驗證器都通過之後,Angular 才會執行非同步驗證器。當每一個非同步驗證器都執行完之後,才會設定這些驗證錯誤。

4.2 內建驗證器函數

在模板驅動表單中用作屬性的那些內建驗證器,比如 required 和 minlength,也都可以作為 Validators 類中的函數使用

 public profileForm = new FormGroup({
    firstName: new FormControl('', [
      Validators.required
    ]),
  });

    this.profileForm.get('firstName').valueChanges.subscribe({
      next: value => {
        console.log("First Name is: " + value);
        console.log(this.profileForm.get('firstName').errors);// { required: true } | null
      }
    });
      <form [formGroup]="profileForm">

        <label>
          First Name:
          <input type="text" formControlName="firstName">
          <div *ngIf="firstName.errors?.required">
            Name is required.
          </div>
        </label>
    </form>

4.3 定義自定義驗證器

內建的驗證器並不是總能精確匹配應用中的用例,因此有時你需要建立一個自定義驗證器。

  public profileForm = new FormGroup({
    firstName: new FormControl('', [
      Validators.required,
      this.forbiddenNameValidator(/bob/i)
    ])
  });

  public forbiddenNameValidator(nameRe: RegExp): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      const forbidden = nameRe.test(control.value);
      return forbidden ? {forbiddenName: {value: control.value}} : null;
    };
  }

  get firstName() { return this.profileForm.get('firstName'); }



      this.profileForm.get('firstName').valueChanges.subscribe({
      next: value => {
        console.log("First Name is: " + value); // First Name is: bob
        console.log(JSON.stringify(this.profileForm.get('firstName').errors));// {"forbiddenName":{"value":"bob"}} | null
      }
    });

4.4 跨欄位交叉驗證

跨欄位交叉驗證器是一種自定義驗證器,可以對錶單中不同欄位的值進行比較,並針對它們的組合進行接受或拒絕。

下列交叉驗證的例子說明了如何進行如下操作:

  • 根據兩個兄弟控制元件的值驗證響應式表單或模板驅動表單的輸入,
  • 當使用者與表單互動過,且驗證失敗後,就會顯示描述性的錯誤資訊

要想在單個自定義驗證器中計算這兩個控制元件,你就必須在它們共同的祖先控制元件中執行驗證: FormGroup。你可以在 FormGroup 中查詢它的子控制元件,從而讓你能比較它們的值。要想給 FormGroup 新增驗證器,就要在建立時把一個新的驗證器傳給它的第二個引數。

    this.profileForm.valueChanges.subscribe( {
      next: value => {
        console.log(JSON.stringify(this.profileForm.errors));// {"identityRevealed":true} | null
      }
    });

  public profileForm = new FormGroup({
    firstName: new FormControl('', [
      Validators.required,
    ]),
    lastName: new FormControl(''),
  }, { validators: this.identityRevealedValidator});

  public identityRevealedValidator(control: FormGroup): ValidationErrors | null{
    const firstName = control.get('firstName');
    const lastName = control.get('lastName');
    return firstName && lastName && firstName.value === lastName.value ? { identityRevealed: true } : null;
  };

4.5 建立非同步驗證器

非同步驗證器實現了 AsyncValidatorFnAsyncValidator 介面。它們與其同步版本非常相似,但有以下不同之處。

  • validate() 函數必須返回一個 Promise 或可觀察物件
  • 返回的可觀察物件必須是有盡的,這意味著它必須在某個時刻完成(complete)。要把無盡的可觀察物件轉換成有盡的,可以在管道中加入過濾操作符,比如 first、last、take 或 takeUntil。

非同步驗證在同步驗證完成後才會發生,並且只有在同步驗證成功時才會執行。如果更基本的驗證方法已經發現了無效輸入,那麼這種檢查順序就可以讓表單避免使用昂貴的非同步驗證流程(例如 HTTP 請求)。

4.6 觸發某個formControlName

    let formControl = this.profileForm.get('firstName');
    formControl.updateValueAndValidity();

更多程式設計相關知識,請存取:!!

以上就是深入瞭解angular中的表單(響應式和模板驅動)的詳細內容,更多請關注TW511.COM其它相關文章!