Search code examples
angularangular-materialangular-material-stepper

How to reset a 'Material Angular Stepper' Step?


I have a Material Angular Stepper (here a stackblitz running example) and I would like to reset the form on each step when the step is loaded. I found in the Material Angular docs that we can use reset() method on MatStep to

Resets the step to its initial state. Note that this includes resetting form data.

But, each time I call this method, it generates an error : Error: list is undefined

Here, a stackblitz running example

app.component.html,

<h1>{{ name }}</h1>
<hr />
<mat-stepper
  #stepper
  class="flex-grow-1"
  (selectionChange)="reloadStep($event)"
>
  <mat-step #s1>
    <ng-template matStepLabel>Username</ng-template>
    <ng-template matStepContent>
      <form>
        <input type="text" placeholder="Username" />
      </form>
    </ng-template>
  </mat-step>

  <mat-step #s2>
    <ng-template matStepLabel>Something else </ng-template>
    <ng-template matStepContent>
      <form>
        <input type="text" placeholder="Something else ..." />
      </form>
    </ng-template>
  </mat-step>

</mat-stepper>

app.component.ts,

import { Component, AfterViewInit, ViewChildren} from '@angular/core';
import { MatStepper, MatStep } from '@angular/material/stepper';
import { StepperSelectionEvent } from '@angular/cdk/stepper';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css'],
})
export class AppComponent implements AfterViewInit {
  name = "Material Angular Stepper";

  @ViewChildren('stepper') stepper!: MatStepper;
  @ViewChildren('s1') one!: MatStep;
  @ViewChildren('s2') two!: MatStep;

  steps: MatStep[] = [];

  ngAfterViewInit(): void {
    this.steps = [this.one, this.two];
  }

  reloadStep = (stepperEvent: StepperSelectionEvent) => {
    const index = stepperEvent.selectedIndex;
    console.log('step changed:', index);
    this.steps[index].reset();
  };
}


Solution

  • There are some changes you must make in your code for it to work as you expect.

    First of all, if you just want to reset the selected step, you can do it in the reloadStep directly, without the need for the MatStep array. This eliminates the list is undefined error:

    reloadStep = (stepper: StepperSelectionEvent) => {
      stepper.selectedStep.reset(); // this line calls the reset on the selected step
    };
    

    For the form resetting part, that needs some extra work. If you are not familiarized with Reactive Forms, this part may be a little confusing. Start by importing the ReactiveFormsModule in your AppModule or the corresponding module:

    // ... Other imports
    // ...
    import { FormsModule, ReactiveFormsModule } from '@angular/forms';
    // ... Other imports
    // ...
    @NgModule({
      imports: [
        // ... 
        FormsModule,
        ReactiveFormsModule,
        // ... 
      ],
      // ... 
    })
    export class AppModule {}
    

    Then, in your component.ts you need to create two FormGroup instances. I'll just put the relevant parts of the code here:

    import { FormBuilder, FormGroup } from '@angular/forms';
    // ...
    
    export class AppComponent implements OnInit {
      // ...
      name = "Material Angular Stepper";
      firstForm: FormGroup;
      secondForm: FormGroup;
    
      constructor(private formBuilder: FormBuilder) {}
    
      ngOnInit(): void {
        // Instantiate the two FormGroups with one control each
        this.firstForm = this.formBuilder.group({
          username: '',
        });
        this.secondForm = this.formBuilder.group({
          somethingelse: '',
        })
      }
    
      // ...
      reloadStep = (stepper: StepperSelectionEvent) => {
        stepper.selectedStep.reset(); // this line calls the reset on the selected step
      };
    
    }
    

    Last but not least, in the template you have to specify the [stepControl] input property of each MatStep, [formGroup] input property of each form and formControlName of each input control to match the name of the control defined in your component.ts file:

      <mat-step #s1 [stepControl]="firstForm">
        <ng-template matStepLabel>Username</ng-template>
        <ng-template matStepContent>
          <form [formGroup]="firstForm">
            <input type="text" placeholder="Username" formControlName="username" />
          </form>
        </ng-template>
      </mat-step>
    
      <mat-step #s2 [stepControl]="secondForm">
        <ng-template matStepLabel>Something else </ng-template>
        <ng-template matStepContent>
          <form [formGroup]="secondForm">
            <input
              type="text"
              placeholder="Something else ..."
              formControlName="somethingelse"
            />
          </form>
        </ng-template>
      </mat-step>
    

    Only then, each time you select a step, it is reset as well as the corresponding form.

    Here's a modified Stackblitz: https://stackblitz.com/edit/angular-ivy-cwkqzx?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fapp.component.ts,src%2Fapp%2Fapp.module.ts