Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

mat-form-field does not remove mat-form-field-invalid class on FormGroup reset #4190

Open
Nathan-Ryan opened this issue Apr 21, 2017 · 70 comments
Labels
area: material/form-field P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent

Comments

@Nathan-Ryan
Copy link

Nathan-Ryan commented Apr 21, 2017

Bug, feature request, or proposal:

Bug

What is the expected behavior?

Upon resetting a FormGroup, I would expect the visual state of the form to be reset also.

What is the current behavior?

The form group and controls are reset but the mat-input-invalid class remains on the md-input-container suggesting that the control is invalid.

What are the steps to reproduce?

This only seems to happen when the [formGroup] attribute is on a form tag. Works as expected on a div tag.

Create component with a FormGroup and a FormControl with Validators.required .
Create a form tag with the [formGroup] attribute.
Add an input control to the form with a required attribute.

When running, fill out the form so its valid, then reset the form and model through the click handler on a button. The required control will now have the mat-input-invalid class.

Which versions of Angular, Material, OS, browsers are affected?

Angular 4.0.1
Material 2.0.0-beta.3
Windows 10
Chrome 57

@Nathan-Ryan Nathan-Ryan changed the title md-input-container does not remove mat-input-invalid class on FromGroup reset md-input-container does not remove mat-input-invalid class on FormGroup reset Apr 21, 2017
@kurpav
Copy link

kurpav commented Apr 22, 2017

Have the same issue 😢

@mmalerba mmalerba self-assigned this Apr 26, 2017
@mmalerba mmalerba added the P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent label Apr 26, 2017
@mmalerba
Copy link
Contributor

@mmalerba
Copy link
Contributor

It looks like this is just a matter of putting type="reset" on your button. Without it, the form is marked as submitted which causes the error styling to show up. see: http://plnkr.co/edit/IUhbPom8FnauivHUW3bU?p=preview

@goelinsights
Copy link

goelinsights commented May 12, 2017

@mmalerba trying to understand how this would fix the issue for a button already type="submit".

I'm resetting after the form is submitted and a response has been received back from the server in an observable complete block.

i.e.,
submitForm()
.subscribe(
res => store.dispatch(type: foo, res),
e => log(e),
() => form.reset()); <- still leaving the form inputs as invalid

@mmalerba
Copy link
Contributor

@goelinsights sounds like maybe a forms issue (could be related to angular/angular#15741). The md-input-container logic is correct, so there's nothing for material to do here

@ghost
Copy link

ghost commented May 12, 2017

Not sure why, using resetForm() method fixed this issue for me.

@zpydee
Copy link

zpydee commented May 13, 2017

@mmalerba not sure i agree with your diagnosis. I am experiencing same issues and have only updated from material-beta2 to material-beta3. Main angular libraries remain unchanged and problem has only been introduced since material upgrade.

It looks like this is just a matter of putting type="reset" on your button. Without it, the form is marked as submitted which causes the error styling to show up.

after form is submitted, error styling should definitely be removed.

@jeroenvanagt
Copy link

jeroenvanagt commented May 30, 2017

@mmalerba Why is this issue closed? The problem still exists when reseting the formGroup like explained by @goelinsights

Scenario:
Press submit button
Do some stuff with the data
Then call formGroup.reset()

formGroup.reset() does NOT remove mat-input-invalid class
screen shot 2017-05-30 at 4 06 33 pm

@willshowell
Copy link
Contributor

@jeroenvanagt try this

<form [formGroup]="myFormGroup" #f="ngForm">
  ...
</form>
@ViewChild('f') myNgForm;

reset() {
  this.myNgForm.resetForm();
}

http://plnkr.co/edit/sRCiYvRqGevK493w79A4?p=preview

@jeroenvanagt
Copy link

@willshowell,

I understand that a reset button works. However, in my case I want to use a submit button, send the data to the backend server and then reset the form (so a new invite can be created).

For the reset, I use :
this.form.reset()
where form is a formGroup

@jeroenvanagt
Copy link

I now use the following workaround

        this.form.reset()

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

@willshowell
Copy link
Contributor

@jeroenvanagt Error state is calculated like this:

isInvalid && (isTouched || isSubmitted)

Unfortunately, resetting the FormGroup isn't enough. You'll need to reset the submitted state of the actual form.

To do that, you need to get access to the FormGroupDirective which binds the FormGroup to the form and then call resetForm() instead of reset().

<form [formGroup]="fg">
  ...
</form>
@ViewChild(FormGroupDirective) myForm;

sendDataToBackendAndResetForm() {
  // TODO: send data to backend
  if (this.myForm) {
    this.myForm.resetForm();
  }
}

See this updated plunker for an example

@Nathan-Ryan
Copy link
Author

@mmalerba I don't think we can call a workaround a resolution. This still is not fixed.

Why can't the FormGroup.reset() method use the FormGroupDirective to clear the submitted state of the form element?

@mmalerba
Copy link
Contributor

@Nathan-Ryan The FormGroup and FormGroupDirective are part of angular core, not angular material. If you want to request changes to the behavior you can file an issue with https://github.com/angular/angular

@goelinsights
Copy link

goelinsights commented Jun 27, 2017 via email

@willshowell
Copy link
Contributor

@goelinsights I may be mistaken but manually removing classes is not the recommended workaround. I'll summarize here:

  1. Submitted state is based on the FormGroupDirective or the NgForm (depending on if you're using template- or model-driven forms). FormGroup does not hold submitted state.

  2. If you wish to reset the form, you must gain access to the FormGroupDirective or the NgForm and call resetForm() on it. That will remove .mat-input-invalid from the input and all associated md-errors

  3. This behavior is in Angular core, not material. You are welcome to propose a change to the behavior, but that should be done in the angular core repository, not here.

  4. If you simply don't want .mat-input-invalid to depend at all on submitted state, you will be able to purpose feat(input): add custom error state matcher #4750 for that once it lands.

@gersonpineda
Copy link

Any Update on this fix?

@biowaffeln
Copy link

The simplest solution isn't resetting the form afterwards, but preventing the form from being submitted in the first place. You can do that by putting a type="button" on the button element, so something like this works as expected in my project:

<button type="button" mat-raised-button (click)="form.reset()">
    reset
</button>

@NJJ2
Copy link

NJJ2 commented Mar 13, 2018

this resolved my issue
https://stackoverflow.com/questions/48216330/angular-5-formgroup-reset-doesnt-reset-validators

@jplew
Copy link

jplew commented May 15, 2018

I think @biowaffeln's answer is the way to go. Simply avoid all references to "Submit" and you're good. To expand on his example:

Before:

<form [formGroup]="createForm" class="create-form" (ngSubmit)="submitForm()">
  <button type="submit" mat-raised-button color="primary" [disabled]="createForm.pristine">Create</button>
</form>

After:

<form [formGroup]="createForm" class="create-form">
  <button (click)="submitForm()" type="button" mat-raised-button color="primary" [disabled]="createForm.pristine">Create</button>
</form>

Your submit function doesn't accept any arguments, it will simply access form values via the class property, so it doesn't make a difference either way:

submitForm() {
  this.myEventEmitter.emit(this.createForm.value)
}

@ondrejpar
Copy link

@jplew an advantage of using (ngSubmit) on form is that the form is automatically submitted when you press Enter in a text field. You would need to do that manually.

@beardedprince
Copy link

I experienced this issue. my only hack was to set

submitted = false;

postComment(id ) {
    this.submitted = true;
    if (this.commentForm.invalid) {
      return;
    }
    this.postService.sendComment(this.id, this.commentForm.value).subscribe(data => {
      console.log(data);
      this.submitted = false; // make submitted false again
    }, err => {
      console.log('error occurred here', err);
    });
  this.commentForm.reset();
    
  }

The above code works for me

@bes1002t
Copy link

I think resetting the directive should be the default behavior of a form field. The issue is open for 3 years, is there any progress so far?

@johnchristopherjones
Copy link

johnchristopherjones commented Aug 31, 2020

TL;dr: use <form novalidate> or you're gonna have a bad time.

So, I was feeling pretty salty about this behavior. Why is this clearly wrong yet nobody is budging? It seemed to me like one of the following should be true:

  1. @angular/core should reset the submitted state with FormGroup#reset().
  2. @angular/core should provide access to the submitted state from FormGroup.
  3. @angular/components really shouldn't be looking at submitted at all, since it's not part of FormGroup et al.

Well, this is just one of those things where everyone is kinda doing the right thing. If you wound up here, what nobody's saying is that the DOM is crazy and your HTML is wrong (MDN):

Note that the submit event fires on the <form> element itself, and not on any <button> or <input type="submit"> inside it. However, the SubmitEvent which is sent to indicate the form's submit action has been triggered includes a submitter property, which is the button that was invoked to trigger the submit request.

The submit event fires when the user clicks a submit button (<button> or <input type="submit">) or presses Enter while editing a field (e.g. <input type="text">) in a form. The event is not sent to the form when calling the form.submit() method directly.

Note: Trying to submit a form that does not pass validation triggers an invalid event. In this case, the validation prevents form submission, and thus there is no submit event.

All of that is to say, if you are using <button>, you are declaring a SUBMIT button, because type="submit" is the default on the <button> tag. If you omitted the novaldiation attribute, then the form IS invalid, you have just attempted to submit it, submitting has provoked form validation per the spec, and the control element has no freaking idea because, you know, the DOM.

Now, why is your HTML wrong? Three reasons:

  1. Per the <button> defaults, you just submitted the form and form submission is a DOM event handled by the <form> element and wrapped by the NgForm directive. You should use novalidate on your <form> to avoid this entire problem in the first place.
  2. You read the @angular/component documentation, which told you to do buttons this way, which in turn led you straight here as soon as you combined validation with buttons without the novalidate option.
  3. Forms also submit when you hit Enter, so you must deal with the validation-upon submit event in that event as well if you omit the novalidate.

The FormGroup really has no idea about any of this because the FormGroup is a pure Angular construct that has nothing to do with DOM APIs. The FormGroupDirective knows about it because it's the jerk that actually talks to the DOM. And now our helpful little mat-form-field comes a long, brings them together, and tells us the form is invalid, which is true! That happened! Yet nobody knows about that submit event except the mat-form-field and the FormGroupDirective it asked.

So, if you can't add novalidate for some reason and you want a button that doesn't submit your form, you should use one of the following:

  1. <button mat-button type="button">Button Label</button>
  2. <button mat-button formnovalidate>Button Label</button> (formnovalidate (MDN)).
  3. <input mat-button type="button" value="Button Label">

That still doesn't solve the Enter problem, so either handle that as well or just use novalidate.

Okay, what if you really want to lean on the native submit/invalid event cycle for some reason? ONLY the NgForm has direct knowledge about submit state, so you need to get a handle on it. You can either inject NgForm in your component (constructor(private form: NgForm){}) or get a handle on a FormGroupDirective that knows how to talk to it (@ViewChild(FormGroupDirective) formGroupDirective). You can then ask it to reset the DOM's form object with resetForm(). That's why you can't just reset() your ReactiveForms AbstractControls: you have to take it up with the DOM.

@tony-yyj
Copy link

Guys try this.

<form` [formGroup]="someFormGorup" (ngSubmit)="onSubmitFunction($event)">
    ...
</form>

onSubmitFunction(event) {
  // TODO: some code
  event.currentTarget.reset()
  this.someFormGorup.reset()
}

I have tried many methods, include FormGroupDirective, don't work.

this worked for me! 😄

but why ? 🤔

@hahalooongboy
Copy link

hahalooongboy commented Feb 8, 2021

Guys try this.

<form` [formGroup]="someFormGorup" (ngSubmit)="onSubmitFunction($event)">
    ...
</form>

onSubmitFunction(event) {
  // TODO: some code
  event.currentTarget.reset()
  this.someFormGorup.reset()
}

I have tried many methods, include FormGroupDirective, don't work.

this worked for me! 😄

but why ? 🤔

I think (and I'm honestly not positive, I'm still learning a lot of this) the $event of ngSubmit is a wrapper to prevent the html form element's default submit event from firing and triggering an HTTP post.

currentTarget is the HTML form, and uses the DOM reset() rather than the FormGroup reset().

@AreyXo
Copy link

AreyXo commented Mar 21, 2021

I can't believe the issue is still open from 2017 to this day!
why do I need to create an ngFormDirective just to reset it?
myFormGroup.reset() should have made it untouched and clean the validators.

@BaneDC
Copy link

BaneDC commented Mar 31, 2021

For me, the simplest solution was the following:

I added variable - submitted to each error in my html file like this and set it initially as false in my .ts file:
*ngIf="submitted && registerFormControl.name.errors?.required"

So in order for the error to be shown, I need to submit my form. When i do submit it and I get the response from the backend, I set it back to false and the errors are gone.

@Lucas00012
Copy link

Lucas00012 commented Apr 1, 2021

Just use (if you can) <div [fomGroup]="form"> instead of <form [fomGroup]="form">
Now the form.reset() cleans the validator state

@fernoliveros
Copy link

fernoliveros commented Apr 19, 2021

Just use (if you can) <div [fomGroup]="form"> instead of <form [fomGroup]="form">
Now the form.reset() cleans the validator state

@Lucas00012 just be aware this takes away other functionality such as validation on submit.

@Totati
Copy link
Contributor

Totati commented Apr 19, 2021

Guys try this.

<form` [formGroup]="someFormGorup" (ngSubmit)="onSubmitFunction($event)">
    ...
</form>

onSubmitFunction(event) {
  // TODO: some code
  event.currentTarget.reset()
  this.someFormGorup.reset()
}

I have tried many methods, include FormGroupDirective, don't work.
this worked for me! 😄
but why ? 🤔

I think (and I'm honestly not positive, I'm still learning a lot of this) the $event of ngSubmit is a wrapper to prevent the html form element's default submit event from firing and triggering an HTTP post.

currentTarget is the HTML form, and uses the DOM reset() rather than the FormGroup reset().

The FormGroupDirective is subscribed to the reset event, so when you call reset on the form element it calls the same resetForm function that willshowell referred. #4190 (comment)

@GowthamSankaran
Copy link

Any update on this issue? I'm still facing this problem. Unfortunately, none of the workarounds worked for me. I reset a form group based on changes made to the control in the parent form group in my component. Upon reset, the values are getting reset, but the material select dropdown still shows invalid.

I tried all the combinations like reset(), setErrors(null), updateValueAndValidity() etc., but no luck.

@AreyXo
Copy link

AreyXo commented Jul 23, 2021

@GowthamSankaran that's unfortunate, although the workarounds are working for me.
Can you share your code to see where is the problem.

@JinjoGlz
Copy link

JinjoGlz commented Aug 19, 2021

none of the answers here worked for me, but plain html/javascript worked, it's a shame bc, why would plain html do something Angular can't?

let form= (<HTMLFormElement>document.getElementById('form'))
form.reset();

@rabhiaziz
Copy link

rabhiaziz commented Oct 28, 2021

this bug still exist until now (angular 12) any updates or idea to solve this issue...
reset doesn't reset validators
form.reset()
i solve it by adding this code

Object.keys(this.contactForm.controls).forEach(key => {
        this.contactForm.controls[key].setErrors(null)
      });

not a good practice but useful,

@woeterman94
Copy link

The error is still present today?

@khyamay
Copy link

khyamay commented Dec 22, 2021

Yup the error is still present and got caught by it today. After googling around, end up here. I used the suggestion type="button" and it is working as expected. I also tried referencing the form and calling .resetForm() but it didn't work.

@BrutalCSkakan
Copy link

Still facing this issue, reset() wont reset the validation.

resetForm() on the directive doesn't solve my issue, however the Object.keys setErrors above works.. Which is redundant as f*** for all of my different components.

@Burtan
Copy link

Burtan commented Mar 8, 2022

I'm also facing this error.

@mrmokwa
Copy link

mrmokwa commented Mar 9, 2022

Anybody knows if the new form (v14) fix this issue?

@paradox37
Copy link

paradox37 commented Mar 15, 2022

The solution is simple here. Add this to your module:

export class CustomMaterialFormsMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null): boolean {
        return !!(control && control.invalid && (control.dirty || control.touched));
    }
}
providers: [
    {
        provide: ErrorStateMatcher, useClass: CustomMaterialFormsMatcher
    }
]

Then you can simply use this.form.reset(), nothing else.

@rajmetti
Copy link

rajmetti commented May 6, 2022

In v13 , setting button type as "button" worked for me.

@danielHin
Copy link

danielHin commented Sep 1, 2022

Hey everyone

Anything new?

Still need to call .setErrors() on the certain form field to get it back into pristine state after resetting the whole form group in v13

this.myFormGroup.get('label').setErrors(null);

@ApplY3D
Copy link
Contributor

ApplY3D commented Nov 24, 2022

The solution is simple here. Add this to your module:

export class CustomMaterialFormsMatcher implements ErrorStateMatcher {
    isErrorState(control: FormControl | null): boolean {
        return !!(control && control.invalid && (control.dirty || control.touched));
    }
}
providers: [
    {
        provide: ErrorStateMatcher, useClass: CustomMaterialFormsMatcher
    }
]

Then you can simply use this.form.reset(), nothing else.

The best solution if you want to reset the form as a side-effect (like after http request), thank you!

Material ErrorStateMatcher bind on form.submitted - source.
So, for multiple submitting, you should provide different error matcher.

Possible (great) solutions - #4190 (comment)

@saimyousuf
Copy link

It looks like this is just a matter of putting type="reset" on your button. Without it, the form is marked as submitted which causes the error styling to show up. see: http://plnkr.co/edit/IUhbPom8FnauivHUW3bU?p=preview

Perfect example of Occam's Razor: The simplest explanation is always the best.

@petogriac
Copy link

2024 and I need to still look for a workaround to this? :(

@jpangburn
Copy link

What works in 2024 with the least extra code

With Angular 18 and reactive forms and standalone components and Material 3, the one that still seems to work is to setup your form like newGateForm = this.formBuilder.group({... then in your submit handler after you've done your submit work:

    // reset the form
    this.newGateForm.reset();
    // this hack is needed to clear any validation errors that are still showing
    // really Angular? 7 years and no clean solution?
    Object.keys(this.newGateForm.controls).forEach(key => {
      //@ts-ignore
      this.newGateForm.controls[key].setErrors(null);
    });

The //@ts-ignore is because you're probably also using TypeScript in a new project and it doesn't like that line. I tried this on Chrome and Safari, both worked. I like this one because there's no changes to the HTML template, just the submit processing code so it keeps the mess contained to one dirty spot on the rug.

What also works

You can also do:

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

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

I kind of like this one because it's clear that the reset takes two steps working on two different parts of the form system, though it's still a workaround for a single form reset. The downside is the mess is spread to both the HTML template and the submit processing code.

What doesn't work

There were a number of ideas that probably worked in the past that I tried that do not seem to work today:

  • <form novalidate... this would have been nice but doesn't seem to help.
  • <button type="reset" or <button type="button" this prevents your users from using the Enter key to submit forms. Users on non-touch devices will despise you for doing this. Do not do this. Always use <button type="submit" as the submit button on a form.
  • <div [formGroup]="form" same problem as reset. Don't do this to your users.
  • (ngSubmit)="onSubmitFunction($event)"> followed by the submit handler calling event.currentTarget.reset(). At least on my system, currentTarget was null so this didn't work.
  • @ViewChild(FormGroupDirective) myForm; then this.myForm.resetForm(). TBH, I didn't really understand how to use this, and the plunker was old enough that it didn't work. It might work if you knew how that directive was supposed to tie to your form.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area: material/form-field P3 An issue that is relevant to core functions, but does not impede progress. Important, but not urgent
Projects
None yet
Development

No branches or pull requests