148

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>
Cœur
  • 37,241
  • 25
  • 195
  • 267
Mike Sav
  • 14,805
  • 31
  • 98
  • 143

6 Answers6

273

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>
Pankaj Prakash
  • 2,300
  • 30
  • 31
FAISAL
  • 33,618
  • 10
  • 97
  • 105
  • 11
    I was just looking at `ViewChild` and seeing how I could reference the Stepper - but you have beaten me to it! Love the fact you added the disable / enable functionality too! Have some points! – Mike Sav Sep 28 '17 at 13:03
  • `ViewChild` is also a good option to get the stepper. But I would prefer passing an id. I have also added `ViewChild` solution in the demo \o/ – FAISAL Sep 28 '17 at 13:08
  • Hi Faisal thanks for such a great answer ,one more help, instead of passing a form to mat-step can we pass angular components and then depending upon that component being valid can I traverse to the next mat-step, how that can be achieved, thankyou – Enthu Nov 15 '17 at 12:14
  • Nice solution. But how could we restrict the scrolling area to just the content of the stepper and not the actual header. If you add heaps of content, then the header will scroll out of sight. Since the header gives an indication of where the user is in some process, then it's important that the header is visible regardless of the amount of content in each step. – Wayne Riesterer Aug 08 '18 at 04:12
  • Here the completed steps icons is set to "EDIT" and the current steps icon is set to "NUMBER" can I have the completed steps icon as "DONE(tick)" and when back button pressed the completed step icon changed to "Edit"....EX: assume I completed step 2 and clicked on next button now the step 2's icon showing as "EDIT icon(pen symbol)" and now I am in step 3 which shows "Number" as icon....What I want to do is the When next btn clicked from step 2 the icon should be "DONE" and 3rd step icon should be "Number",if I clicked back btn of 3rd step the icon of 3rd step should be "DONE" 2nd is "EDIT". – Zhu Aug 21 '18 at 05:44
  • Any clue how to get this working when using ng-if on mat-horizontal-stepper? – Paul Strupeikis Oct 06 '19 at 22:19
  • On having the `[linear]="true"` attribute on my stepper, I can't programmatically call the next one. Is there a reason why? – Seba M Feb 22 '20 at 12:57
  • 1
    If you are using Angular 8, and using @ViewChild, you should use _static: false_ `@ViewChild('stepper', {static: false}) private myStepper: MatStepper;` Per Angular, this value will default to _false_ in Angular 9+ [link](https://angular.io/guide/static-query-migration) – rorvis Dec 09 '20 at 17:27
  • (goBack(){ this.myStepper.previous(); } ) What about itsunit test? Can we have that too? – Satyam Singh Jul 11 '21 at 08:14
  • This solution also works if the stepper is in a mat-dialog-content in which case the matStepperNext doesn't work. – Gabor Jan 18 '22 at 23:49
46

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.

Alec Gerona
  • 2,806
  • 1
  • 24
  • 24
33

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.

BlackBeard
  • 10,246
  • 7
  • 52
  • 62
5

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();
}
Christoph Lütjen
  • 5,403
  • 2
  • 24
  • 33
3

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;
  }
}
M.Laida
  • 1,818
  • 1
  • 13
  • 19
  • 1
    I am just useing `@ViewChild('stepper') private myStepper: MatStepper;` and than `this.matStepper.next();` in my function. Working perfectly – Max Apr 16 '19 at 15:28
0

The solution I implemented allows previous step validations (it fills the validation dots):

  ngAfterViewInit() {
        if(this.isDone) {
          this.stepper.selectedIndex = 0;
          this.stepper.linear = false;
    
          // remove the validation // which makes the next call to the next method validate the step. warning => the validation should be readded.
          this.firstFormGroup = this._formBuilder.group({
            email: ['',],
            password:['',]
          });
    
          this.stepper.next();
          
          this.secondFormGroup = this._formBuilder.group({
            pricingYear: ['',],
            pricingMontly: ['',],
          });
          this.stepper.next();
    
          setTimeout(() => {
            this.stepper.linear = true;
         });
        }
      }
Nearon
  • 71
  • 5