5

I am using Angular Material with Angular 7. I have a number of reactive forms, each in their own control, and each updating different property collections of a primary object being edited. These controls are each hosted in a different step of the Angular Material Stepper control.

Each control has a button called "Add New", which allows the user to add a new object to the collection, as well as an "Edit" button to edit any existing item in the collection. Whenever the "Add New" button is clicked, I create a new item with default values (most empty), and bind it to the reactive form in the control.

The Form always allows the user to click an "Add to Collection" button (i.e., the button is never disabled), and validation errors, if any, are shown to the user at that time. (The item is not added to the collection if there are validation errors.)

Everything works fine until I go to another step of the Stepper control and then return the the step I'm working on. When that happens, and I click the "Add New" button, I see all of the required validation errors immediately, even if the user does not click the "Add to Collection" button. When I look at the value of a control, I don't see any reason the validation errors should be showing, but yet they are (red highlight around the empty inputs).

Here is the code I am running whenever a user clicks "Add New":

public resetForm(formGroup: FormGroup) {
if (!formGroup)
  return;

  formGroup.reset();
  formGroup.clearValidators();
  // see https://stackoverflow.com/questions/48216330/angular-5-formgroup-reset-doesnt-reset-validators
  Object.keys(formGroup.controls).forEach(key => {
    if (ignoreControls.indexOf(key) == -1) {
      formGroup.get(key).setErrors(null);
      formGroup.get(key).markAsPristine();
      formGroup.get(key).markAsUntouched();
      formGroup.get(key).clearValidators();
      formGroup.get(key).updateValueAndValidity();
    }
  }); 

When I look at an individual control, in the html, I see everything I expect to see:

<input _ngcontent-wyd-c14="" class="mat-input-element mat-form-field-autofill-control cdk-text-field-autofill-monitored ng-untouched ng-pristine ng-invalid" formcontrolname="title" matinput="" placeholder="e.g. Customer Service Representative" required="" type="text" ng-reflect-required="" ng-reflect-name="title" ng-reflect-placeholder="e.g. Customer Service Represen" ng-reflect-type="text" id="mat-input-24" aria-invalid="true" aria-required="true">

Note that the class contains ng-untouched and ng-pristine (as well as ng-invalid). If a field is pristine and untouched, it should not be showing validation errors.

What I suspect is that changing to a different step on the Stepper control causes some kind of form submission or submission attempt, and that is why all the errors show (form.submitted == true or something like that). But I don't know how to reset anything to treat the form as if it is brand new.

Can anyone help?

(Note: I have seen a number of SO articles suggesting that I need to call resetForm on the form directive, as opposed to the formGroup (see, e.g., Angular 5 FormGroup reset doesn't reset validators). But, when I add #formDirective="ngForm" to my form tag, and add to my typescript:

@ViewChild('formDirective') private formDirective: NgForm;

and I then call this.formDirective.resetForm(), it makes no difference - the validation errors still display immediately.

UPDATE: Here is a stackblitz which basically shows the problem. Click "Save and Continue" from the first step, then click "Add New" in the second step. Form appears with no validation errors. Now click on the "Intro" step of the stepper, and then click back to the "Experiences" step, you will see that all of the validation appears when you return to the step. This is the problem in my actual production project - a user should be able to navigate from one step to the other without triggering validation. In the experiences component, you can see am trying to reset the form every time the intiForm method is called, but you still see the errors. Why? Link: https://stackblitz.com/edit/angular-w4iunu

JRS
  • 569
  • 9
  • 26

1 Answers1

3

What I suspect is that changing to a different step on the Stepper control causes some kind of form submission or submission attempt, and that is why all the errors show (form.submitted == true or something like that).

You're right here. Click on "Add New" button causes form to be submitted because any button in HTML by default has type submit. Once you click on Add New button your form gets submitted flag.

The solution should be simple:

 <button mat-raised-button color="primary" (click)="addNew()" type="button"...>Add New</button>
                                                               ^^^^^^^^^
                                                              add this

Forked Stackblitz

Another issue here is that Stepper takes into account interacted property of step when calculating error status for control. Once you move from one step to another it becomes true.

You can workaround this behavior by resetting this property on selectionChange:

app.component.html

<mat-horizontal-stepper #diaryStepper ... (selectionChange)="stepIndexChanged(diaryStepper)"

app.component.ts

stepIndexChanged(stepper) {
  stepper.steps.toArray().forEach(step => step.interacted = false)
}

Updated Stackblitz

yurzui
  • 205,937
  • 32
  • 433
  • 399
  • I made this change, and it does indeed prevent the validation from happening when I click "Add New" the *first time* I go to the experiences step. But if you click "Add New", then click on the "Intro" step of the stepper, and then click back to the "Experiences" step, you will see that all of the validation appears when you return to the step. This is the problem in my actual production project - a user should be able to navigate from one step to the other without triggering validation. – JRS Aug 11 '19 at 20:11
  • @JRS I agree with you. Please take a look at the updated answer. – yurzui Aug 11 '19 at 20:50
  • That did it! My production app is now working as expected. Thank you so much! Out of curiosity, where did you find this information? – JRS Aug 12 '19 at 13:22