MurabitoB

Angular Reactive Form

N 人看过

在上一篇 Angular Template Form | MurabitoB’Blog

講到了 Angular 的 Template Form,這篇要講的是 Reactive Form

首先要引用 Reative Form 的方法須先引入 ReactiveFormModule

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    ReactiveFormsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

接著,可以在 Component 裡面宣告 FormGroup,並且在 ngOnInit 的時候起始化 ,使其在 template 被渲染前先起始化。

  • FormGroup
    • 用來代表整個表單
    • 也可以在表單內部的子表單,做成巢狀結過
  • FormControl
    • 代表表單內部的控制項
    • 第一個參數代表起始值
    • 第二個參數的陣列代表驗證項
      • 驗證項目可以給陣列或者單一的驗證器
    • 第三個參數代表的是非同步的驗證器
  • FormArray
    • 表單內不的陣列,裡面存放的是複數的 FormControl , FormGroup
signupForm: FormGroup;

  ngOnInit() {
    this.signupForm = new FormGroup({
      userData: new FormGroup({
        username: new FormControl(null, [
          Validators.required,
          this.forbiddenNames.bind(this), // use javascript trick to make sure bind the Angular but not this class
        ]),
        email: new FormControl(
          null,
          [Validators.required, Validators.email],
          this.forbiddenEmails
        ),
      }),
      gender: new FormControl('male'),
      hobbies: new FormArray([]),
    });
  }

與 Template Form 不同的是, Reative Form 須指定 Template 的 form 所代表的 formGroup

一樣透過 ngSubmit 來接收 submit 事件

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

如果資料像上面的範例是用巢狀的方式組起來,那也要針對下層的母元素進行分組動作,可以使用 formGroupName 來進行設定

要注意在 Template Form 所使用的 directive 是 ngModelGroup,兩者是不同的。

<div formGroupName="userData"></div>

接著在表單內 input 元素就可以進行 FormControl 的設定

<input
  type="text"
  id="username"
  formControlName="username"
  class="form-control"
/>

如果要設定 FormArray 則是

<div formArrayName="hobbies"></div>

與 Template Form 相同的是,表單的狀態也會以下列內容添加到 class 上

# true flase
點擊過 ng-invalid ng-untouched
值有變過 ng-dirty ng-pristine
值驗證通過 ng-valid ng-invalid

取得數值

直接取用 formGroup.value 就可以了

this.signupForm.value;

自訂驗證器

Reactive Form 的自訂驗證器只要寫一個 function,接收 FormControl 作為參數,根據結果回傳 `{property : boolean}

範例

forbiddenNames(control: FormControl): { [s: string]: boolean } {
  if (this.forbiddenUsernames.indexOf(control.value) !== -1) {
    return { nameIsForbidden: true };
  }
  return null;
}

此外,有些驗證是透過呼叫 API 進行驗證,這時候可以使用 Promise 或者 Observable 進行非同步的處理。

forbiddenEmails(control: FormControl): Promise<any> | Observable<any> {
  const promise = new Promise<any>((resolve, reject) => {
    setTimeout(() => {
      if (control.value === 'test@test.com') {
        resolve({ emailIsForbidden: true });
      } else {
        1;
        resolve(null);
      }
    }, 1500);
  });
  return promise;
}

錯誤提示

如果是要針對各錯誤進行不同的錯誤訊息提示,可以用以下作法

<span
  *ngIf="!signupForm.get('userData.username').valid && signupForm.get('userData.username').touched"
  class="help-block"
>
  <span *ngIf="signupForm.get('userData.username').errors['nameIsForbidden']"
    >This name is invalid!</span
  >
  <span *ngIf="signupForm.get('userData.username').errors['required']"
    >This field is required!</span
  >
</span>

訂閱變化

valuesChange 可以訂閱所有數值的變化
statusChange 可以訂閱狀態的變化

  • INVALID 錯誤
  • PENDING 非同步驗證正在執行中
  • VALID 驗證通過
this.signupForm.valueChanges.subscribe((value) => console.log(value));
this.signupForm.statusChanges.subscribe((status) => console.log(status));

設定數值

作法跟 Template Form 一樣有以下兩種方式

  1. setValue
  2. patchValue

patchValue 可以只傳需要修改的內容,而 setValue 會覆蓋掉整個內容。

this.signupForm.setValue({
  userData: {
    username: "Max",
    email: "max@test.com",
  },
  gender: "male",
  hobbies: [],
});

this.signupForm.patchValue({
  userData: {
    username: "Anna",
  },
});