113

I have a form on my page and when I call FormGroup.reset() it sets the forms class to ng-pristine ng-untouched but FormControl.hasError(...) still returns truthy. What am I doing wrong here?

Template

<form [formGroup]="myForm" (ngSubmit)="submitForm(myForm)">
  <mat-form-field>
    <input matInput formControlName="email" />
    <mat-error *ngIf="email.hasError('required')">
      Email is a required feild
    </mat-error>
  </mat-form-field>
  <mat-form-field>
    <input matInput type="password" formControlName="password" />
    <mat-error *ngIf="password.hasError('required')">
      Password is a required feild
    </mat-error>
  </mat-form-field>
  <button type="submit">Login</button>
</form>

Component

export class MyComponent {
  private myForm: FormGroup;
  private email: FormControl = new FormContorl('', Validators.required);
  private password: FormControl = new FormControl('', Validators.required);

  constructor(
    private formBuilder: FormBuilder
  ) {
    this.myForm = formBuilder.group({
      email: this.email,
      password: this.password
    });
  }

  private submitForm(formData: any): void {
    this.myForm.reset();
  }
}

Plunker

https://embed.plnkr.co/Hlivn4/

10 Rep
  • 2,217
  • 7
  • 19
  • 33
efarley
  • 8,371
  • 12
  • 42
  • 65
  • 1
    Can you try also calling `this.myForm.markAsUntouched();`? – Explosion Pills Jan 11 '18 at 21:52
  • That does not work and should not be necessary based on the documentation. (https://v2.angular.io/docs/ts/latest/api/forms/index/FormGroup-class.html) – efarley Jan 11 '18 at 22:15
  • Possible duplicate of [Angular NgForm: reset exact form filed value does not make it valid](https://stackoverflow.com/questions/48026810/angular-ngform-reset-exact-form-filed-value-does-not-make-it-valid) – Hugo Noro Jan 12 '18 at 00:43
  • Does this answer your question? [How to reset form validation on submission of the form in ANGULAR 2](https://stackoverflow.com/questions/34608361/how-to-reset-form-validation-on-submission-of-the-form-in-angular-2) – trees_are_great Nov 19 '20 at 09:41

14 Answers14

216

It (FormGroup) behaves correctly. Your form requires username and password, thus when you reset the form it should be invalid (i.e. form with no username/password is not valid).

If I understand correctly, your issue here is why the red errors are not there at the first time you load the page (where the form is ALSO invalid) but pop up when you click the button. This issue is particularly prominent when you're using Material.

AFAIK, <mat-error> check the validity of FormGroupDirective, not FormGroup, and resetting FormGroup does not reset FormGroupDirective. It's a bit inconvenient, but to clear <mat-error> you would need to reset FormGroupDirective as well.

To do that, in your template, define a variable as such:

<form [formGroup]="myForm" #formDirective="ngForm" 
  (ngSubmit)="submitForm(myForm, formDirective)">

And in your component class, call formDirective.resetForm():

private submitForm(formData: any, formDirective: FormGroupDirective): void {
    formDirective.resetForm();
    this.myForm.reset();
}

GitHub issue: https://github.com/angular/material2/issues/4190

Harry Ninh
  • 16,288
  • 5
  • 60
  • 54
  • This issue really needs to be solved, like you said this is pretty inconvenient to have to use this workaround... Will do with it, good catch & thanks for the solution. – schankam Sep 18 '18 at 04:39
  • 3
    In my case, I had a very unique situation where I needed to reset the "submitted" without clearing the form values (which resetForm()) was doing to me. To get around this, I did `(formDirective).submitted = false;` . Kind of a dirty hack but looking at the source code there's no obvious reason submitted needed to be readonly in their typescript definition. – scourge192 Sep 21 '18 at 14:01
  • 2
    Read the github issue where the official response was "not my department". Pretty lame, I expect better from google employees. – ctilley79 Feb 02 '19 at 05:09
  • With angular 7.2.2: `Argument of type NgForm is not assignable to type FormGroupDirective` – msanford Feb 22 '19 at 18:55
  • Works with Angular 8.x as well! – stayingcool Feb 04 '20 at 08:04
  • > It (FormGroup) behaves correctly. well it depends, I don't think it works correctly. form.reset() should "Reset" the whole form. Instead, it clears the values but leaves the errors. If we look at from 'what documentation says' point of view it works. but it does change the fact that whoever wrote the reset function was 'lazy' and this is just a half-baked function... – dvdmn Feb 19 '21 at 20:28
  • 2
    Still not fixed in Angular Material 12 (since about 4 years). So this workaround still needed. Shame on you Angular Material devs! – Silvos Jul 12 '21 at 14:03
42

In addition to Harry Ninh's solution, if you'd like to access the formDirective in your component without having to select a form button, then:

Template:

<form 
  ...
  #formDirective="ngForm" 
>

Component:

import { ViewChild, ... } from '@angular/core';
import { NgForm, ... } from '@angular/forms';

export class MyComponent {
 ...
 @ViewChild('formDirective') private formDirective: NgForm;

  constructor(... )

  private someFunction(): void { 
    ...
    formDirective.resetForm();
  }
}
Maximillion Bartango
  • 1,533
  • 1
  • 19
  • 34
  • 1
    I think this is a better answer. the one marked as the answer needs to pass a local local variable to the code behind, not desirable. But that answer has provided very good information around how form works. – Sam Apr 11 '19 at 23:15
  • For Angular 8, The `@ViewChild directive takes two params. The other parameter, apart from the string 'formDirective' is metadata properties. See https://angular.io/api/core/ViewChild#description for more info. – Jonathan Cardoz Jan 27 '20 at 18:08
  • great answer, but his not only resets validators, but also resets all values (entered by users) in the form. – ukie Oct 26 '20 at 15:29
  • This is the only answer that worked for me in Angular 7 + material – laffuste Jan 21 '21 at 09:53
39

After reading the comments this is the correct approach

// you can put this method in a module and reuse it as needed
resetForm(form: FormGroup) {

    form.reset();

    Object.keys(form.controls).forEach(key => {
      form.get(key).setErrors(null) ;
    });
}

There was no need to call form.clearValidators()

Mauricio Gracia Gutierrez
  • 10,288
  • 6
  • 68
  • 99
Savio Rodrigues
  • 423
  • 4
  • 2
7

Add the property -

@ViewChild(FormGroupDirective) formGroupDirective: FormGroupDirective;

and use this instead of this.myForm.reset();

this.formGroupDirective.resetForm();

This will reset the error display and also do the job of form.reset(). But the form, along with the fields, will still show ng-invalid class

Check this answer for more details - https://stackoverflow.com/a/56518781/9262627

Abhinav
  • 75
  • 1
  • 9
5

The below solution works for me when trying to reset specific form controller in form group -

 this.myForm.get('formCtrlName').reset();
 this.myForm.get('formCtrlName').setValidators([Validators.required, Validators.maxLength(45), Validators.minLength(4), Validators.pattern(environment.USER_NAME_REGEX)]);
 this.myForm.get('formCtrlName').updateValueAndValidity();
Sayan
  • 481
  • 5
  • 12
3

form.reset() won't work on custom form control like Angular Material that's why the function is not working as expected.

My workaround for this is something like this

    this.form.reset();
    for (let control in this.form.controls) {
      this.form.controls[control].setErrors(null);
    }

this.form.reset() the issue with this is that it will reset your formcontrol values but not the errors so you need to reset them individually by this line of code

for (let control in this.form.controls) {
      this.form.controls[control].setErrors(null);
    }

With this you don't need to use FormGroupDirective which is a cleaner solution for me.

Github issue: https://github.com/angular/angular/issues/15741

brijmcq
  • 3,354
  • 2
  • 17
  • 34
3

UPDATE FROM 2021 - ANGULAR 11.2

The fact to use a [formGroup]="form and a #formDirective="ngForm" directly into the HTML function is not a good practise. Or maybe you would prefer to use @ViewChild, and do it directly from your .ts. Actually, the problem don't come from Angular, but Material.

If you take a look at their GitHub, you will see this :

/** Provider that defines how form controls behave with regards to displaying error messages. */
@Injectable({providedIn: 'root'})
export class ErrorStateMatcher {
  isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
    return !!(control && control.invalid && (control.touched || (form && form.submitted)));
  }
}

The form will keep its submitted state. So you just have to delete the last part of the function. Here is my solution (tested and working). I have a Material Module, into I've implemented this :

export class ShowOnInvalidTouchedErrorStateMatcher implements ErrorStateMatcher {
  isErrorState(control: FormControl): boolean {
    return !!(control && control.invalid && control.touched);
  }
}

@NgModule({
  providers: [
    {
      provide: ErrorStateMatcher, useClass: ShowOnInvalidTouchedErrorStateMatcher
    }
  ],
  exports: [
    MatSnackBarModule,
    MatTabsModule,
    ...
  ]
});

If you want to use this ErrorStateMatcher on only one form, it's possible. Please see this Material example. This is the same principle.

Emilien
  • 2,701
  • 4
  • 15
  • 25
2

I found that after calling resetForm() and reset(), submitted was not being reset and remained as true, causing error messages to display. This solution worked for me. I found it while looking for a solution to calling select() and focus() on an input tag, which also wasn't working as expected. Just wrap your lines in a setTimeout(). I think setTimeout is forcing Angular to detect changes, but I could be wrong. It's a bit of a hack, but does the trick.

<form [formGroup]="myFormGroup" #myForm="ngForm">
    …
    <button mat-raised-button (click)="submitForm()">
</form>
submitForm() { 
    …
    setTimeout(() => {
        this.myForm.resetForm();
        this.myFormGroup.reset();
    }, 0);
}
  • Found this answer after spending entire day. setTimeout() helped to resolve this. If you can add some description about how this 'hack' is working , will be helpful. Tested with angular 7.2.8 – Rajendra Thorat Nov 08 '19 at 16:22
2
 resetForm() {
    this.myFormGroup.reset();
    this.myFormGroup.controls.food.setErrors(null);
    this.myFormGroup.updateValueAndValidity();
  } 
S. V
  • 1,085
  • 5
  • 5
  • 2
    While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Andreas Jun 18 '20 at 04:29
1

I had no luck with resetting the form directive. But You can also change the input state to pending to do that as well.

this.myForm.get("email").reset();
this.myForm.get("password").reset();
Janith
  • 2,730
  • 16
  • 26
1

To anyone whom this may help, I am running Angular 9.1.9 and I didn't want to reset the form/controls just the overall validity of the form so I just ran:

this.registerForm.setErrors(null);

...where registerForm: FormGroup and that reset the form errors, leading to:

this.registerForm.valid

...returning true.

The same can be done for controls:

this.registerForm.get('email').setErrors(null)

As soon as the form is touched, these errors are re-evaluated anyway so if that's not good enough, you may need to have a boolean flag to further pin-down exactly when you want to start showing/hiding error UI.

I did not need to touch the directive in my case.

GoForth
  • 573
  • 7
  • 10
0

I was also having the same set of problems. My problem was that i was using mat-form-field and formGroup. After resetting the form submitted flag was not resetting.

So, the solution that worked for me is, putting a directive of ngForm along with formGroup and passing onSubmit(form). Added @ViewChild('form') form; in component and then I used this.form.resetForm();

prisar
  • 3,041
  • 2
  • 26
  • 27
0

Nothing from above worked for me (Angular 7.2, Angular Material 7.3.7).

Try to pass with submit method an event on view:

<form [formGroup]="group" (ngSubmit)="onSubmit($event)">
    <!-- your form here -->
</form>

Then use it to reset currentTarget and your form afterwards:

public onSubmit(event): void {
  // your code here
  event.currentTarget.reset()
  this.group.reset()
}
mpro
  • 14,302
  • 5
  • 28
  • 43
0

Simple fix: use button with type="reset" and function submitForm() together

<form [formGroup]="MyForm" (ngSubmit)="submitForm()">
  <input formControlName="Name">
  <mat-error>  
    <span *ngIf="!tunersForm.get('Name').value && tunersForm.get('Name').touched"></span>  
  </mat-error>
  <button type="reset" [disabled]="!MyForm.valid" (click)="submitForm()">Save</button>
</form>
j3ff
  • 5,719
  • 8
  • 38
  • 51