Can I programmatically move the steps of a mat-horizontal-stepper in Angular / Angular Material

AngularAngular MaterialAngular Material2

Angular Problem Overview


I have a question regards Angular Material (with Angular 4+). Say in my component template I add a <mat-horizontal-stepper> component, and within each step <mat-step> I have stepper buttons to navigate the component. Like so...

<mat-horizontal-stepper>
  <mat-step>
    Step 1
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
  <mat-step>
    Step 2
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
  <mat-step>
    Step 3
    <button mat-button matStepperPrevious type="button">Back</button>
    <button mat-button matStepperNext type="button">Next</button>
  </mat-step>
</mat-horizontal-stepper>

Now I am wondering if it is possible to remove the buttons out of each step and have them elsewhere in the <mat-horizontal-stepper> in a static position or even outside the <mat-horizontal-stepper> and I can navigate backwards and forwards using code within my component typescript file. To give an idea, I would like my HTML be something like this

<mat-horizontal-stepper>
	<mat-step>
		Step 1
	</mat-step>
	<mat-step>
		Step 2
	</mat-step>
	<mat-step>
		Step 3
	</mat-step>
	<!-- one option -->
	<div>
	   <button mat-button matStepperPrevious type="button">Back</button>
	   <button mat-button matStepperNext type="button">Next</button>
	</div>
</mat-horizontal-stepper>

<!-- second option -->
<div>
   <button (click)="goBack()" type="button">Back</button>
   <button (click)="goForward()" type="button">Next</button>
</div>

Angular Solutions


Solution 1 - Angular

Yes. It is possible to jump to a specific stepper by using selectedIndex property of the MatStepper. Also, MatStepper exposes public methods next() and previous(). You can use them to move back and forth.

In your template:

Add an id to your stepper e.g. #stepper. Then in your goBack() and goForward() methods, pass the stepper id:

<mat-horizontal-stepper #stepper>
    <!-- Steps -->
</mat-horizontal-stepper>    
<!-- second option -->
<div>
   <button (click)="goBack(stepper)" type="button">Back</button>
   <button (click)="goForward(stepper)" type="button">Next</button>
</div>

.. and in your typescript:

import { MatStepper } from '@angular/material/stepper';

goBack(stepper: MatStepper){
    stepper.previous();
}

goForward(stepper: MatStepper){
    stepper.next();
}

Link to stackblitz demo.


You can also use ViewChild to get a reference to the stepper component in your TypeScript as shown below:

@ViewChild('stepper') private myStepper: MatStepper;

goBack(){
    this.myStepper.previous();
}

goForward(){
    this.myStepper.next();
}

In this case, you don't have to pass the stepper reference in the method in your component's html. Link to Demo with ViewChild


You can enable/disable the Back and Next buttons by using the following:

<button (click)="goBack(stepper)" type="button" 
        [disabled]="stepper.selectedIndex === 0">Back</button>
<button (click)="goForward(stepper)" type="button" 
        [disabled]="stepper.selectedIndex === stepper._steps.length-1">Next</button>

Solution 2 - Angular

In addition to @Faisal's answer, this is my take on making the MatStepper jump without needing to pass the stepper in the arguments.

This is helpful when you need more flexibility in manipulating the stepper e.g. from a Service or something else.

HTML:

<div fxLayout="row" fxLayoutAlign="center center" fxLayoutGap="6px">
  <button (click)="move(0)">1st</button>
  <button (click)="move(1)">2nd</button>
  <button (click)="move(2)">3rd</button>
  <button (click)="move(3)">4th</button>
</div>

TS File:

move(index: number) {
    this.stepper.selectedIndex = index;
}

Here's the stackblitz demo.

Solution 3 - Angular

If you want to navigate programmatically to next step and if you are using a linear stepper, follow the below steps:

  • Create a stepper like this: <mat-horizontal-stepper linear #matHorizontalStepper>

  • Define mat-step like this: <mat-step [completed]="isThisStepDone">

  • From inside mat-step create a button to go to next step like this: <button (click)="next(matHorizontalStepper)">NEXT STEP</button>

  • In .ts file declare a MatStepper reference named stepper :
    @ViewChild('matHorizontalStepper') stepper: MatStepper;

  • Also, within .ts file initialize isThisStepDone as false : isThisStepDone: boolean = false;

  • Then write method for NEXT STEP button named next():

     submit(stepper: MatStepper) {
      this.isThisStepDone = true;
      setTimeout(() => {           // or do some API calls/ Async events
       stepper.next();
      }, 1);
     }
    

NOTE: The async part (setTimeout()) is required due to state propagation via isThisStepDone.

Solution 4 - Angular

You could also do it by specifying the actual index of the stepper using selectedIndex.

stackblitz: https://stackblitz.com/edit/angular-4rvy2s?file=app%2Fstepper-overview-example.ts

HTML:

<div class="fab-nav-container">
   <mat-vertical-stepper linear="false" #stepper>
       <mat-step *ngFor="let step of stepNodes; let i = index">
           <ng-template matStepLabel>
               <p> {{step.title}} </p>
           </ng-template>
       </mat-step>
   </mat-vertical-stepper>
</div>

<div class="button-container">
   <div class="button-grp">
      <button mat-stroked-button (click)="clickButton(1, stepper)">1</button>
      <button mat-stroked-button (click)="clickButton(2, stepper)">2</button>
      <button mat-stroked-button (click)="clickButton(3, stepper)">3</button>
   </div>
</div>

TS:

import {Component, OnInit, ViewChild} from '@angular/core';
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
import { MatVerticalStepper } from '@angular/material';
import { MatStepper } from '@angular/material';
export interface INodes {
    title: string;
    seq: number;
    flowId: string;
}
/**
 * @title Stepper overview
 */
@Component({
  selector: 'stepper-overview-example',
  templateUrl: 'stepper-overview-example.html',
  styleUrls: ['stepper-overview-example.scss'],
})
export class StepperOverviewExample implements OnInit {
  @ViewChild(MatVerticalStepper) vert_stepper: MatVerticalStepper;
  @ViewChild('stepper') private myStepper: MatStepper;

  stepNodes: INodes[] = [
    { title: 'Request Submission', seq: 1, flowId: 'xasd12'}, 
    { title: 'Department Approval', seq: 2, flowId: 'erda23'}, 
    { title: 'Requestor Confirmation', seq: 3, flowId: 'fsyq51'}, 
  ];

  ngOnInit() {
  }
  ngAfterViewInit() {
    this.vert_stepper._getIndicatorType = () => 'number';
  }
  clickButton(index: number, stepper: MatStepper) {
      stepper.selectedIndex = index - 1;
  }
}

Solution 5 - Angular

If you are inside of child components, you can inject the stepper.

MyMainPageWithStepper.html (simplified)

<mat-horizontal-stepper>
  <mat-step label="Upload">
    <my-component></my-component>
  </mat-step>
</mat-horizontal-stepper>

MyComponent.ts

constructor(private readonly _stepper: CdkStepper}{}

someFunction(): void {
   this._stepper.next();
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMike SavView Question on Stackoverflow
Solution 1 - AngularFaisalView Answer on Stackoverflow
Solution 2 - AngularAlec GeronaView Answer on Stackoverflow
Solution 3 - AngularBlackBeardView Answer on Stackoverflow
Solution 4 - AngularM.LaidaView Answer on Stackoverflow
Solution 5 - AngularChristoph LütjenView Answer on Stackoverflow