29

i am using angular material 6 ,i have a vaidation inside mat-form-field mat-error is not displayed , when move after mat-form-field to the mat-error which is displaying properly.

Not Working code:

 <mat-form-field>
<input matInput type="time" formControlName="ToTime"/> <mat-error *ngIf="DaterForm.get('ToTime').hasError('InValidToTime')">FromTime Should be less than ToTime if choose a same date</mat-error>
     </mat-form-field>

Working Fine:

 <input matInput type="time" formControlName="ToTime"/> </mat-form-field>
 <mat-error *ngIf="DaterForm.get('ToTime').hasError('InValidToTime')">FromTime Should be less than ToTime if choose a same date</mat-error>

Some one explain why which is not working inside that control.

Live Demo: stackblitz

Mohamed Sahir
  • 2,482
  • 8
  • 40
  • 71

4 Answers4

35

Yes, mat-error does not show up by default. It only shows when the input is touched.

But, luckily you can override this behavior using errorStateMatcher input property, bound to mat-input element.

The pull request in which this feature was added.

Usage :

<mat-form-field>
    <input matInput [errorStateMatcher]="matcher" [matDatepicker]="picker" placeholder="Choose a Start date" 
    formControlName="FromDate"
      [min]="minFromDate" 
           [max]="maxToDate" >
    <mat-datepicker-toggle matSuffix [for]="picker" ></mat-datepicker-toggle>
    <mat-datepicker #picker></mat-datepicker>
    <mat-error >Please provide a valid Fromdate</mat-error> 
  </mat-form-field> 

So you have to implement ErrorStateMatcher in your code this way.

export class MyErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    const isSubmitted = form && form.submitted;
    return (control && control.invalid);
  }
}

And in your component add a new object matcher for ErrorStateMatcher class, which will act as a value to [errorStateMatcher]="matcher"

matcher = new MyErrorStateMatcher();

I have also added the same code in your forked stackblitz

Suggestion :

You need not provide a ngIf condition for mat-error specifying your formControlName. It will be automatically considered based on the mat-form-field in which it is present.

Amit Chigadani
  • 28,482
  • 13
  • 80
  • 98
  • thanks for the valuable suggestion,Is there any other way to do the same behaviour ?But in your solution your have removed the NgIf directive in mat error , i don't want to display a error message by default when form gets touched which not met the particular condition need to show the error message ,eg)` FromTime Should be less than ToTime if choose a same date ` – Mohamed Sahir Jul 21 '18 at 15:25
  • 1
    You have nothing specific in `*ngIf`. You are doing the same thing what `mat-error` will do. It is redundant. And If you dont want to display error before it is touched, thats what `mat-error` does by default. – Amit Chigadani Jul 21 '18 at 15:31
  • ok got it i have removed the `*ngIF` and tried this with errorstateMatcher and Without ErrorstateMatcher then why which is not working `To Time: FromTime Should be less than ToTime if choose a same date `. here is a forked :https://stackblitz.com/edit/angular-customvalidator-gbcjep?file=app/datepicker-overview-example.html – Mohamed Sahir Jul 21 '18 at 15:36
  • 1
    That is probably because you have not mentioned your time `Validators` against your `toTime` form control as a part of `formGroup`. `mat-error` will not get to know the global `Validators`. You should specify the `Validators` clearly against each formControl if you expect them to be shown by `mat-error` – Amit Chigadani Jul 21 '18 at 15:46
  • `*ngIf="DaterForm.get('ToTime').hasError('InValidToTime')"` will never be true, because your error is never bound to `ToTime` form control. – Amit Chigadani Jul 21 '18 at 16:00
  • You can try adding errors manually to your form control https://stackoverflow.com/questions/43553544/how-can-i-manually-set-an-angular-form-field-as-invalid – Amit Chigadani Jul 21 '18 at 16:13
  • I am looking for this as per your answer mat error does not know the global validators, and already get the error message when provide the validators against particular control , if i do that i am getting particular control values , therefore i have written the global validators after that i have get this problem is there any other better way to do this ?, i need all the formcontrol values in particular To time controlchanges l, therefore i have written the global validators but which is not shows the error there , thanks for the valuable comments.I will update my question – Mohamed Sahir Jul 21 '18 at 16:17
  • You can modify that as a custom Validator against toTime formControl as opposed to global Validator. – Amit Chigadani Jul 21 '18 at 16:32
  • ErrorStateMatcher is exactly what I was looking for. My UI designer didn't want the error state to appear when the form was still pristine. Happens when you just focus, don't add input and remove focus. Using this I can only show error state if the form control is not pristine anymore. Thanks a lot. – Anjil Dhamala Apr 21 '21 at 14:07
4

I Found a very simple solution without overriding the ErrorStateMatcher Class, simply you could import in the app.module.ts

1- Import libraries:

import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material/core'; 

2- Add to providers like:

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

Re-Serve the app.

Updated on 1/2/2021 by Reza Taba

There is an issue with the above short solution though: If the user leaves the field blank after being touched (blur), the required error is not shown.

See my solution for the fix.

Reza Taba
  • 1,151
  • 11
  • 15
  • 2
    Not true. Dirty is the opposite of pristine. `get dirty(): boolean { return !this.pristine; }` Touched is just whether it has had focus (tabbed through or using mouse/touch). And something can become dirty programatically without ever being touched. – Simon_Weaver Jan 04 '19 at 02:37
  • The source here is useful to make sure your assumptions are correct. Yes it can be confusing! https://github.com/angular/angular/blob/1b0b36d14322a77e67a106991174db36b1c8bcd7/packages/forms/src/model.ts – Simon_Weaver Jan 04 '19 at 02:37
  • While what sherif proposed is true, for lazy loaded modules this doesn't work. There is an open issue: https://github.com/angular/components/issues/10084 – Victor Muresanu Sep 21 '20 at 15:31
1

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

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 Add The Below Function :

validateAllFormFields(formGroup: FormGroup) {
    Object.keys(formGroup.controls).forEach(field => {
        const control = formGroup.get(field);
        if (control instanceof FormControl) {
            control.markAsTouched({ onlySelf: true });
        } else if (control instanceof FormGroup) {
            this.validateAllFormFields(control);
        }
    });
}

And call It in ngOnInit function with your FormGroup , Like Below :

    this.validateAllFormFields(this.submittedForm);