angular / components

Component infrastructure and Material Design components for Angular
https://material.angular.io
MIT License
24.39k stars 6.76k forks source link

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

Open Nathan-Ryan opened 7 years ago

Nathan-Ryan commented 7 years ago

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

kurpav commented 7 years ago

Have the same issue 😢

mmalerba commented 7 years ago

plunker repro: http://plnkr.co/edit/UguaoP1sTR85pyZLh2TN?p=preview

mmalerba commented 7 years ago

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 commented 7 years ago

@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 commented 7 years ago

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

ghost commented 7 years ago

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

zpydee commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

@jeroenvanagt try this

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

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

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

jeroenvanagt commented 7 years ago

@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 commented 7 years ago

I now use the following workaround

        this.form.reset()

        Object.keys(this.form.controls).forEach(key => {
          this.form.controls[key].setErrors(null)
        });
willshowell commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

@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 commented 7 years ago

@mmalerba the hacky workaround is just to manually remove .mat-input-invalid on submission. If that's the desired approach, we should put it into the docs as it's a fairly straightforward way to address what should be a common problem for a reusable form that is reset on submission (that from my perspective isn't an expected or obvious output).

On Wed, Jun 21, 2017 at 12:01 PM, mmalerba notifications@github.com wrote:

@Nathan-Ryan https://github.com/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

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/angular/material2/issues/4190#issuecomment-310174269, or mute the thread https://github.com/notifications/unsubscribe-auth/ACv1DSsUuegP-lRLkdGOj5o1CoY8DEAUks5sGWh_gaJpZM4NDvLu .

--


Vijay Goel, M.D. Principal, Goel Insights 310-492-5601 www.goelinsights.com

willshowell commented 7 years ago

@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 https://github.com/angular/material2/pull/4750 for that once it lands.

gersonpineda commented 7 years ago

Any Update on this fix?

biowaffeln commented 7 years ago

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 commented 6 years ago

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

jplew commented 6 years ago

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 commented 6 years ago

@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.

chachan commented 6 years ago

@NaveenJayaram94 created an infinite loop

nickwinger commented 6 years ago

Actually this is still a bug only in material. It has nothing to do with resetting the submit state.

In angular core i can put markAsPristine and markAsUntouched on a single formControl. The css classes get set correctly ignoring if it was submitted or not.

The question is why put css class mat-form-field-invalid on it, when there is already ng-invalid. The "red-state" could be calculated like this: ng-invalid && ng-touched finish, it would work.

krmgopi commented 5 years ago

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

I am also having this issue after reset the form controls are reset but mat-errors are showing invalid

mmalerba commented 5 years ago

There could be some change detection issue with the mat-form-field-invalid class. I'll reopen for investigation

christophschoeni commented 5 years ago

Will the problem be solved in the next 1 - 2 releases? Since the problem already exists since 2017?

eugensunic commented 5 years ago

bump

ctilley83 commented 5 years ago

I love the "not my department" attitude here. The workaround mentioned in this stackoverflow answer should be the default behavior

https://stackoverflow.com/questions/48216330/angular-5-formgroup-reset-doesnt-reset-validators

ccamba commented 5 years ago

Versions: "@angular/animations": "^7.2.2", "@angular/cdk": "^7.3.2", "@angular/common": "^7.2.4", "@angular/compiler": "^7.2.4", "@angular/core": "^7.2.4", "@angular/forms": "^7.2.4", "@angular/material": "^7.3.2"

This work for me, for this scenario; I have and material stepper and the user can start de process if he likes, then before show the first step I reset de form calling form():

template: <form [formGroup]="formGroup"> <button type="button" (click)="submitForm()">

ts: ngOnInit() { this.formGroup = this._formBuilder.group(new MyForm()); }

submitForm() { let control: AbstractControl = null; Object.keys(this.formGroup.controls).forEach((name) => { control = this.formGroup.controls[name]; control.updateValueAndValidity(); }); }

reset() { let control: AbstractControl = null; this.formGroup.reset(new DatosGeneralesForm());

  setTimeout(() => {
    this.formGroup.markAsUntouched();
    Object.keys(this.formGroup.controls).forEach((name) => {
      control = this.formGroup.controls[name];
      control.reset();
      control.setErrors(null);
    });
  }, 100);

}

bruno-kremer commented 5 years ago

it seems this is still an issue.

the workaround from @willshowell worked for me, but had to go through some research to get this fixed. Thinking it could be fixed with alternatives already provided here, or at least better documented.

emadkheiralla commented 5 years ago

I now use the following workaround

        this.form.reset()

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

This worked for me. Thanks

MatthiasKunnen commented 5 years ago

@emadkheiralla, while that works for a 1 level structure, it won't work for nested controls. You would need to create a recursive function for that. It also is not recommended, see https://github.com/angular/material2/issues/4190#issuecomment-311488176.

alexandrebeato commented 5 years ago

Same problem here.

billyjov commented 5 years ago

Same Problem here

NarekGrigoryan commented 5 years ago

Guys try this.

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

onSubmitFunction(event) {
  // TODO: some code
  event.currentTarget.reset()
  this.someFormGorup.reset()
}
bjscatena commented 5 years ago

Guys try this.

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

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

This worked for me. Thanks

sapabg commented 5 years ago

I had the same problem but with a single FormControl() without a FormGroup. After .reset() and .setErrors(null) the validator stops working until .updateValueAndValidity(); So basically it is: someControl.reset() someControl.setErrors(null) someControl.updateValueAndValidity();

I think when you are using FormGroup you'll need someFormGroup.updateValueAndValidity()...

hfournier commented 5 years ago

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(). 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);
}
sapabg commented 5 years ago

@hfournier have you tried updateValueAndValidity()?

hfournier commented 5 years ago

@hfournier have you tried updateValueAndValidity()?

@sapabg Yes. It didn't work.

sapabg commented 5 years ago

this might help: https://stackblitz.com/edit/angular-nxuztu

lianmaung commented 5 years ago

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

Thanks. this solves my problem.

aWeinzierl commented 4 years ago

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(). It's a bit of a hack, but does the trick.

Did not help.

But this helped me:

Guys try this.

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

onSubmitFunction(event) {
  // TODO: some code
  event.currentTarget.reset()
  this.someFormGorup.reset()
}
sdktraceur commented 4 years ago

Angular 9 this problem still exists!

beardedprince commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 4 years ago

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 commented 3 years ago

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 commented 3 years ago

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.