1
votes

Both mat-error show when only one error appeared.

I'm trying to make custom validators with mat-error. Both input for email and confirm password are red when each of them has a true value for hasError('').

I think my MyErrorStateMatcher class logic is wrong somehow. Please help! I have tried anything I could. Thank you in advance!

Image

As you can see in the image. When confirmPassword throws an error, the email field is also red.


My ErrorStateMatcher:

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const invalidCtrl = !!(control && control.invalid && (control.dirty || control.touched));
    const invalidParent = !!(control && (control.parent.hasError('notTheSame') || control.parent.hasError('emailUsed')));

    return ((invalidCtrl || invalidParent));
  }
}

HTML: (focus on email and confimPassword)

<form [formGroup]="signupForm" (ngSubmit)="signup(signupForm)">
        <mat-form-field style="width: 100%;">
          <input matInput formControlName="email" placeholder="Email address" type="email" [errorStateMatcher]="matcher" required>
          <mat-error *ngIf="signupForm.controls.email.hasError('required')">Email required!</mat-error>
          <mat-error *ngIf="signupForm.controls.email.hasError('email')">Email invalid!</mat-error>
          <mat-error *ngIf="signupForm.hasError('emailUsed')">This email already exists!</mat-error>
        </mat-form-field>
        <mat-form-field style="width: 100%;">
          <input matInput formControlName="username" placeholder="User name" (blur)="signupForm.value.username != null ? isValidUsername(signupForm.value.username) : ''" required />
          <mat-error>Please enter your new username!</mat-error>
          <mat-error *ngIf="usernameInvalid">Username already exists!</mat-error>
        </mat-form-field>
        <mat-form-field style="width: 100%;">
          <input matInput formControlName="password" placeholder="New password" [type]="show ? 'text' : 'password'" required />
          <mat-icon matSuffix (click)="show = !show" style="cursor: pointer;">{{show ? 'visibility' : 'visibility_off'}}</mat-icon>
          <mat-error>Please enter your password!</mat-error>
        </mat-form-field>
        <mat-form-field style="width: 100%;">
          <input matInput formControlName="confirmPassword" placeholder="Confirm password" type="password" [errorStateMatcher]="matcher" required>
          <mat-error *ngIf="signupForm.controls.confirmPassword.hasError('required')">Please confirm your password!</mat-error>
          <mat-error *ngIf="signupForm.hasError('notTheSame') && signupForm.value.confirmPassword != ''">Password is not the same!</mat-error>
        </mat-form-field>
        <br>
      <button mat-raised-button class="sessionBtn" color="primary" [disabled]="signupForm.invalid">Submit!</button>
</form>

TS:

signupForm = new FormGroup({
    firstName: new FormControl(),
    lastName: new FormControl(),
    email: new FormControl('', [
      Validators.required,
      Validators.email
    ]),
    username: new FormControl(),
    password: new FormControl('', [
      Validators.required
    ]),
    confirmPassword: new FormControl('', [
      Validators.required
    ])
  }, { validators: [this.checkPassword, this.checkExistingEmail] });
  matcher = new MyErrorStateMatcher();

/////////Custom validator////////

  checkPassword(signupForm: FormGroup) {
    let password = signupForm.value.password;
    let confirmPassword = signupForm.value.confirmPassword;

    return password === confirmPassword ? null : { notTheSame: true };
  }

  checkExistingEmail(signupForm: FormGroup) {
    let inputEmail = signupForm.value.email;
    let dbEmail = "[email protected]";

    return inputEmail !== dbEmail ? null: { emailUsed: true };
  }

Error happend with input email and input confirmPassword, both have [errorStateMatcher]="matcher"

2
apart from @G. Tranter answer another approach is you can use pattern validator and pass regex to check duplicate email - indrajeet
You must understand the aim of has a customErrorMatches. see this great link itnext.io/…: A <mat-error> only show if the input inside the <mat-form-field> is invalid. Using a customErrorMatches you can say that the mat-error is showed in a different state (in the link show when the form has an error and the control is dirty ¡althought the control is valid!). In your code, all input that has [errorStateMatcher]="matcher" becomes invalid if is invalid the form, so remove the [errorStateMatcher] in your email input - Eliseo
@Eliseo it might be a good idea to remove them, but I need it for later to make a custom validator for the email itself to check duplicate email in the database. Is there a way to combine all custom validator into one errorStateMatcher? - Nhok V
If your checkExistingEmail only depend of email, put the validator in the email (not in the form), if depends others fields, you can use two different customErrorMatcher - Eliseo
You mean do it like this?: email: new FormControl('', [ Validators.required, Validators.email, this.checkExistingEmail <== put this here? ]) Is it a good practice to make 2 different customErrorMatcher. What if I have many validators? - Nhok V

2 Answers

3
votes

The email address field shows an error because the error state matcher checks the parent - which is the form - which is in error because the password fields do not match. You need to use different error state matchers for the email field and password fields because the conditions are different - email does not need to be in error if the password fields don't match.

2
votes

Creating a customErrorMatcher

Well, if we want to show an error in a <mat-form-field> when the input is valid, we use a customErrorMatcher.

This is a class like

class CrossFieldErrorMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    //when we want to show the error
    return true 
    //when we want not show the error
    return false
  }
}

Normally we has in our component

  errorMatcher=new CrossFieldErrorMatcher()
  //and the .html
  <mat-form-field>
    <input matInput formControlName='verifyPassword' 
        [errorStateMatcher]="errorMatcher">
    <mat-error *ngIf="....">
      Passwords do not match!
    </mat-error>
  </mat-form-field>

Well, we are change the things a bit, adding a constructor in our customErrorMatcher

class CrossFieldErrorMatcher implements ErrorStateMatcher {
  constructor(private name:string){}  //<--add a constructor
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    //when we want to show the error, but we can use "name"
    return true 
    //when we want not show the error
    return false
  }
}

Then, our component becomes

  errorMatcher(name:string)
  {
    return new CrossFieldErrorMatcher(name);
  }

  //and the .html
  <mat-form-field>
    <input matInput formControlName='verifyPassword' 
        [errorStateMatcher]="errorMatcher('password')">
    <mat-error *ngIf="....">
      Passwords do not match!
    </mat-error>
  </mat-form-field>