DEV Community

Cover image for How to use errorStateMatcher in Angular material
Dzmitry Hutaryan
Dzmitry Hutaryan

Posted on

How to use errorStateMatcher in Angular material

We use form all the time and set up validation rules for it. Angular provides a good built-in solution for that. But today I'd like to talk about angular material form fields more.

There is nothing special when we use typically validation with mat-form-field. We just put error message inside mat-error component and set up which messages should be displayed for which errors.


ℹ️ Error messages are shown when the control is invalid and the user has interacted with (touched) the element or the parent form has been submitted.


Let's take a look a bit more complex example. I reckon that comparison two passwords fields will be a good example because we should compare value for both fields.
Let say we need to show error that passwords don't match under confirmPassword field only.


ℹ️ It's not difficult to show error under the field. But we need to say our field about this error to make it to be red. Otherwise, the field will know nothing about the error.


First of all we need to create a form with two fields. I will put all code into one file. You can see all at once.

import { Component } from '@angular/core';
import { bootstrapApplication } from '@angular/platform-browser';
import { provideAnimations } from '@angular/platform-browser/animations';
import {
  FormControl,
  FormGroup,
  ReactiveFormsModule,
  Validators,
} from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import 'zone.js';

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [
    ReactiveFormsModule,
    MatFormFieldModule,
    MatInputModule,
    MatButtonModule,
  ],
  template: `
    <form [formGroup]="form">
      <mat-form-field appearance="outline">
        <mat-label>Password</mat-label>
        <input formControlName="password" matInput/>
        @if (hasError('password', 'required')) {
          <mat-error>Field is required</mat-error>
        }
      </mat-form-field>

      <mat-form-field appearance="outline">
        <mat-label>Confirm password</mat-label>
        <input formControlName="confirmPassword" matInput/>
        @if (hasError('confirmPassword', 'required')) {
          <mat-error>Field is required</mat-error>
        }
      </mat-form-field>

      <button color="primary" mat-flat-button>Save</button>
    </form>
  `,
})
export class App {
  readonly form = new FormGroup({
    password: new FormControl<string | null>(null, Validators.required),
    confirmPassword: new FormControl<string | null>(null, Validators.required),
  });

  hasError(controlName: string, error: string): boolean {
    return this.form.get(controlName).hasError(error);
  }
}

bootstrapApplication(App, {
  providers: [provideAnimations()],
});
Enter fullscreen mode Exit fullscreen mode

We have set up our form. Now, we need to create and add validator to form.
This validator will compare values for two controls what's names we'll pass and return ValidatorFn.

function matchValidator(
  controlName: string,
  compareWith: string
): ValidatorFn | null {
  return (form: FormGroup) => {
    const value = form.get(controlName).value;
    return value && value === form.get(compareWith).value
      ? null
      : { match: true }; // error type
  };
}
Enter fullscreen mode Exit fullscreen mode

Then we need to add our validator to the form.

readonly form = new FormGroup(
    {
      password: new FormControl<string | null>(null, Validators.required),
      confirmPassword: new FormControl<string | null>(
        null,
        Validators.required
      ),
    },
    { validators: [matchValidator('password', 'confirmPassword')] }
  );
Enter fullscreen mode Exit fullscreen mode

Let's check it out to be sure. We should see { match: true } object for error property inside the form.

Form error state

Everything works as expected. What's the next? We can show error messages under the field. But how can we get the field become red? How to tell our input about error state? That's why errorStateMatcher exist.


ℹ️ An ErrorStateMatcher must implement a single method isErrorState which takes the FormControl for this matNativeControl as well as the parent form and returns a boolean indicating whether errors should be shown. (true indicating that they should be shown, and false indicating that they should not.)


@Injectable({ providedIn: 'root' })
export class CrossFieldErrorMatcher implements ErrorStateMatcher {
  public isErrorState(
    control: FormControl | null,
    form: FormGroupDirective | NgForm | null
  ): boolean {
    return form.errors?.match && (control.touched || control.dirty);
  }
}
Enter fullscreen mode Exit fullscreen mode
<mat-form-field appearance="outline">
  <mat-label>Confirm password</mat-label>
  <input formControlName="confirmPassword" matInput [errorStateMatcher]="matcher"/>
  @if (hasError('confirmPassword', 'required')) {
    <mat-error>Field is required</mat-error>
  }
  @if (form.hasError('match')) {
    <mat-error>Password don't match</mat-error>
  }
</mat-form-field>
Enter fullscreen mode Exit fullscreen mode

I used @Injectable decorator here to inject state matcher to the component. That's it.

Final result:

Is there a way to use errorStateMatcher globally?

Yes, there is. For example, you want to change default behavior and show error only if form has been submitted. Let's create errorStateMatcher for that.

export class SubmittedStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return !!(control?.invalid && isSubmitted);
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we need to provide to your app module it instead of default ErrorStateMatcher.

providers: [
  {
    provide: ErrorStateMatcher,
    useClass: SubmittedStateMatcher,
  },
]
Enter fullscreen mode Exit fullscreen mode

Hope, this article will help you with your validation 🙂

Top comments (1)

Collapse
 
jangelodev profile image
João Angelo

Dzmitry Hutaryan, Good tip!
Thanks for sharing