Angular Material – 在 mat-error 自定義 validator 顯示錯誤
Angular Material – 在 mat-error 自定義 validator 顯示錯誤

Angular Material – 在 mat-error 自定義 validator 顯示錯誤

[Angular Material] - 在 mat-error 自定義 validator 顯示錯誤

問題:

使用了 validator(自定義的驗證器) ,檢查表單中兩個欄位的輸入值是否相等,validator 在一般的表單上會正確的顯示錯誤,但使用在 mat-form-fieldmat-error 卻無法正確顯示錯誤。

mat-error-00.png

原因 :

mat-form-flied 內的 mat-error 只會出現在 FormControl 判斷為 " 無效 " 的狀態。

所以當 validator (自定義的驗證器) 沒有通過,但 FormControl 的狀態並不是 " 無效 ",就會造成驗證沒過應該要顯示的 mat-error 顯示不出來。

解決方法:

使用 Angular Material 提供 errorStateMatcher directive,自定義 mat-inputFormControl 有效與無效狀態。


補充:

遇到這個問題的時候,百思不得其解,明明 validator 的驗證邏輯沒有錯,*ngIF 大小寫也沒寫錯 ( 笑,怎麼用了 mat-error 就是無法正確顯示,在查了很久看了很多篇文章後,發現 Angular material 官網有以下這樣一段話:

The <mat-form-field> allows you to associate error messages with your matInput. By default, these error messages are shown when the control is invalid and either the user has interacted with (touched) the element or the parent form has been submitted.

什麼意思呢 ? <mat-form-field> 可以讓你連結 matInput和錯誤訊息,但是只有在表單控制元件的狀態為無效、使用者為輸入狀態、整個表單已經送出過,這三種狀態下錯誤訊息才會被顯示。

也就是說寫在 mat-error 的錯誤訊息無法顯示的原因,有可能是不在這三種狀態下所以沒有顯示,雖然 validator 有回傳錯誤。

經過了幾次嘗試後,發現控制 confirmPassword 控制元件 (FromControl) 的狀態一直是"有效",而 validator 驗證完的錯誤結果是回傳給 confirmPassword 的爸爸 passwordFormpasswordForm 控制元件 (FromGroup)的狀態是 "無效",但是顯示錯誤的條件是 confirmPassword 的狀態要是"無效" ,這就是問題的所在。

mat-error-01.png

參考:

Display custom validator error with mat-error

MatError & Cross-Field Validators In Angular Material 7


出現問題:

StackBlitz Code

  • app.component.html
<h2 class="mat-title">Password Validator</h2>
<form class="example-form" [formGroup]='passwordForm' appConfirmPassword>

  <mat-form-field>
    <input matInput placeholder="Password" formControlName='password'>
  </mat-form-field>

  <mat-form-field>
    <input matInput placeholder="ConfirmPassword" formControlName='confirmPassword'>
    <mat-error *ngIf="passwordForm.errors?.confirmFault">
      Passwords do not match!
    </mat-error>
  </mat-form-field>

</form>
  • app.component.ts
import { Component } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { confirmPasswordValidator } from './confirm-password.directive';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  passwordForm: FormGroup;

  constructor(private fb: FormBuilder) {
    this.initForm();
  }

  initForm() {
    this.passwordForm = this.fb.group({
      password: ['', Validators.required],
      confirmPassword: ['',Validators.required]
    }, {
      validator: confirmPasswordValidator
    })
  }
  get password(){ return this.passwordForm.get('password'); }
  get confirmPassword(){ return this.passwordForm.get('confirmPassword');}
}
  • confirm-password.directive.ts
import { Directive } from '@angular/core';
import { AbstractControl, FormGroup, NG_VALIDATORS, ValidationErrors, Validator, ValidatorFn } from '@angular/forms';

@Directive({
  selector: '[appConfirmPassword]',
  providers: [{ provide: NG_VALIDATORS, useExisting: ConfirmPasswordDirective, multi: true }]
})

export class ConfirmPasswordDirective implements Validator{

  validate(control: AbstractControl): ValidationErrors {
    return confirmPasswordValidator(control);
  }
}

export const confirmPasswordValidator: ValidatorFn = (control: FormGroup): ValidationErrors | null => {

  const password = control.get('password');
  const confirmPassword = control.get('confirmPassword');

  return password.dirty && confirmPassword.dirty && password.value !==         confirmPassword.value ? {confirmFault: true} : null;
}

解決方法:

StackBlitz Code

  • ErrorStateMatcher
class PasswordErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return control.touched && form.invalid;
  }
}
  • app.component.ts
errorMatcher = new PasswordErrorStateMatcher();
  • mat-form-fieldinput 中,新增 errorStateMatcher
<mat-form-field>
    <input matInput placeholder="ConfirmPassword" formControlName='confirmPassword'  [errorStateMatcher]='errorMatcher'>
    <mat-error *ngIf="passwordForm.errors?.confirmFault">
      Passwords do not match!
    </mat-error>
  </mat-form-field>