How to check if matInput displayed an error

1.8k Views Asked by At

I try to display an error message and error icon when matInput has an error (controlled by a formControl). But if the form is submited without touch the field, error is not displayed.

View code playground on stackblitz

Or here is my code:

app.component.ts

ngOnInit() {
    this.formGroup = new FormGroup({
      email: new FormControl(null, [Validators.required, Validators.email]),
    });
}
get emailHasError() {
    return (
      !this.formGroup.get('email').valid && this.formGroup.get('email').touched
    );
}

app.component.html

<form [formGroup]="formGroup" class="form">
  <mat-form-field class="form-element">
    <input matInput placeholder="Email address" formControlName="email" />
    <mat-error *ngIf="emailHasError">
      {{ errorEmail }}
    </mat-error>
  </mat-form-field>
  <div class="form-element">
    <button mat-raised-button color="primary" type="submit" class="button">
      Submit Form
    </button>
  </div>
</form>

However, the matInput field turns red and seems to receive the error.

I read some solution that suggests to toggle a property isSubmit on form submit. But seems to be ugly and and this requires passing this property to the child component if the form is split into several components.

How does Angular Material manage to detect if the form is submitted in order to display the error?

EDIT:

The only solution I found for now is to check classList with @ViewChild as follow. But it still seems to be a hacky way... On stackblitz I process as follow:

@ViewChild('matInput') matInput: ElementRef;
get hasError() {
    return this.matInput?.nativeElement?.classList?.contains('ng-invalid');
}
<input
  matInput
  #matInput
  placeholder="Email address"
  formControlName="email"
/>
2

There are 2 best solutions below

0
On

A custom ErrorStateMatcher may be a neat solution for this problem.

  1. Create a custom ErrorStateMatcher like so:
class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(
    control: FormControl | null,
    form: FormGroupDirective | NgForm | null
  ): boolean {
    const isSubmitted = form && form.submitted;
    return !!(
      control &&
      control.invalid &&
      (control.dirty || control.touched || isSubmitted)
    );
  }
}
  1. Add it to your component class
  matcher = new MyErrorStateMatcher();
  1. Use it in your template
  <mat-form-field class="form-element">
    <input
      matInput
      placeholder="Email address"
      formControlName="email"
      [errorStateMatcher]="matcher"
    />
    <mat-error *ngIf="formGroup.invalid">
      <mat-icon mat-suffix color="warn">error</mat-icon>
      {{ errorEmail }}
    </mat-error>
  </mat-form-field>

The ErrorStateMatcher controls exactly which set of conditions should display an error response in the UI. In the current example, it will display whenever isSubmitted is true, even if the form control is not "dirty" or "touched".

Here's a stackblitz demo.

3
On

No need to return emailHasError() and errorEmail(), and checking touched inside component, angular material input field automatically checks that and shows mat-error only if a field is touched.

Instead directly check errors in html and show the message accordingly.

See working demo here

Added this css in styles.css to show only first error if there are more than one -

/* shows only first error */
.mat-error:not(:first-of-type) {
  display: none !important;
}