52

I am trying to do validation using the <mat-form-field> and <mat-error>. This works fine when user tabs out of the input without filling. But how do I force this error to show when I click a button? I am not using submit. Also, using template-driven forms.

This is my code:

HTML:

<mat-form-field>
    <input matInput placeholder="Due Date" name="dueDate" [(ngModel)]="dueDate" [formControl]="dueDateValidator" required>
    <mat-error *ngIf="dueDateValidator.invalid">Due Date is required for Tasks</mat-error>
</mat-form-field>

TS:

dueDateValidator: FormControl = new FormControl('', [Validators.required]);

Jan Nielsen
  • 10,892
  • 14
  • 65
  • 119
ganeshk
  • 5,583
  • 3
  • 27
  • 31

10 Answers10

54

See how to use a form with a custom ErrorStateMatcher

If you wish to override this behavior (e.g. to show the error as soon as the invalid control is dirty or when a parent form group is invalid), you can use the errorStateMatcher property of the matInput. The property takes an instance of an ErrorStateMatcher object. An ErrorStateMatcher must implement a single method isErrorState which takes the FormControl for this matInput 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.)

I would make a separate file such as default.error-matcher.ts

/** Error when invalid control is dirty or touched*/
export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return !!(control && control.invalid && (control.dirty || control.touched));
  }
}

Then in the TS file add:

matcher = new MyErrorStateMatcher();

Then change the input to use matcher:

<mat-form-field>
    <input matInput placeholder="Due Date" name="dueDate" [(ngModel)]="dueDate" [formControl]="dueDateValidator" [errorStateMatcher]="matcher" required>
    <mat-error *ngIf="dueDateValidator.invalid">Due Date is required for Tasks</mat-error>
</mat-form-field>
Kyle Pfromer
  • 1,485
  • 1
  • 15
  • 26
  • Advanced: if you're creating your own custom form controls the `[errorStateMatcher]` is part of the `CanUpdateErrorState` interface – Simon_Weaver Jan 15 '19 at 07:10
33

Since you want to show mat error on button's click, please try the below: For Angular6 version:

  1. Import the required classes
import { FormControl, FormBuilder, FormGroup } from '@angular/forms';
  1. Declare form control in your component.ts file:
nameControl = new FormControl('');
  1. Add the control in html:
<mat-form-field  style="width: 100%" floatPlaceholder="never">
    <input matInput placeholder="your placeholder text" [formControl]="nameControl" required/>
    <mat-error *ngIf="nameControl.errors?.required">name is required</mat-error>
</mat-form-field>
  1. Add this to your button click event handler:
this.nameControl.markAsTouched();

It's important to check how you are using the form control, ".markAsTouched()" on point 4 will show the mat error for the corresponding form control.

geisterfurz007
  • 5,292
  • 5
  • 33
  • 54
Simran kaur
  • 378
  • 3
  • 7
  • @Simran What is the function of '?' in errors?.required – Dipendra Gurung May 16 '19 at 14:05
  • @DipendraGurung it checks whether the error arisen for the control is for "required", if the control error is for required validation then error message should appear stating "name is required" similarly error can be checked for the pattern. – Simran kaur Jul 09 '19 at 13:10
  • 3
    I used ```this.nameControl.setErrors({ invalid: true });``` and ```Some error message!``` – About7Deaths Jul 17 '19 at 16:23
22

Angular 8 has a new forms method: markAllAsTouched();

This will mark a control/form and ALL DESCENDANTS as touched!!!

So:

this.form.markAllAsTouched();

Is the solution.

Spock
  • 2,482
  • 29
  • 27
9

This works for me. :) On button's click:

this.nameControl.markAsTouched();
Tejashree
  • 750
  • 12
  • 14
7

I am providing 3 different solutions for different scenarios, use the one which suits you.

  • If you are using a form, then do

    this.form.markAllAsTouched();
    
  • If you need a particular field to be affected inside form, then filter that nameControl and do

    nameControl.markAsTouched();
    
  • If you are not using forms, then specify a ref for the input element and initialize variable in ts file & do as follows,

    @ViewChild('myInputRef') myInputRef; // Initialize the ref of input element
    .
    .
    this.myInputRef.control.markAsTouched()
    
Ganesh
  • 1,820
  • 2
  • 20
  • 40
4

Either you can do as "Kyle Pfromer" suggested or as you are using form group, you can mark element as touched on submit with

onSubmit(){ this.formName.get('formControlName').markAsTouched(); }
NikhilKrt
  • 76
  • 2
  • 3
3

Based on Kyle Pfromer's post, I found my solution (to the same problem):

In the TS file I added the StateMatcher after I found an invalid form, eg.

if (this.myFormGroup.invalid) {
  this.matcher = new MyErrorStateMatcher();
  return;
}

In the MyErrorStateMatcher class I changed as following:

    return !!(control && control.invalid);

I find it confusing that Angular Material is not detecting the error anyway.

snibbo
  • 179
  • 11
2

GLOBALLY: Show mat-error while typing or touched: Unlike the provided solution, this method will take care of all mat-errors in the app without applying the matcher to each input.

1- Create touched-error-state.matcher.ts file:

import {FormControl, FormGroupDirective, NgForm } from '@angular/forms';
import {ErrorStateMatcher} from '@angular/material/core';

export class TouchedErrorStateMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
        return !!(control && control.invalid && (control.dirty || control.touched));
    }
}

2- In app.module import:

import { ErrorStateMatcher } from '@angular/material/core';
import { TouchedErrorStateMatcher } from './your-folder-path/touched-error-state.matcher';

3- Now provide it into the providers:

@NgModule({
  providers: [
    AuthService,
    UserService,
    { provide: ErrorStateMatcher, useClass: TouchedErrorStateMatcher }
  ],
})

4- Re-Serve the app.

Reza Taba
  • 1,151
  • 11
  • 15
0

You can also easily call the AbstractControl.updateValueAndValidity() function on button click. This will run the validation process on the corresponding ForControl again and show errors, if there are some (based on your Validators).

So, in your example:

    checkForErrorsOnButtonClick(): void {
      dueDateValidator.updateValueAndValidity();
    }
0

the easiest way is call markUserNameTouched() method as below on button click on template. We use markAsTouched() on formControl.

public staffLoginForm: FormGroup;
ngOnInit(){
this.staffLoginForm = new FormGroup({
username: new FormControl(null),
password: new FormControl(null)});

markUserNameTouched():void{
  this.staffLoginForm.get('username').markAsTouched();
}
vijay
  • 609
  • 5
  • 9