angular material stepper: disable header navigation
Asked Answered
G

16

66

I want to navigate the stepper only through the next and back buttons.

I can't get this to work since users can also click each step label to navigate to any step. I can't use linear, since it requires each step to have a formArray or FormGroup.

I have tried <mat-step (click)="$event.stopPropagation()">.

Ghislainegholston answered 25/10, 2017 at 13:38 Comment(1)
Did you have any success with finding a solution?Pervious
D
128

Add this to your style sheet. I was trying to disable the header navigation. Tried many things but this hack worked. You can try this till Angular Material Team support this feature.

::ng-deep .mat-horizontal-stepper-header{
    pointer-events: none !important;
}
Donkey answered 6/11, 2017 at 17:59 Comment(8)
This does not work, I can still navigate to previous step with the headerCoblenz
@Coblenz Kindly add this chunk of code at the end of your main style.css or style.scss. It will workDonkey
@ShahzaibShahid Why does adding it to the component's scss file not work? Why does it have to be the shared style.scss file?Bertberta
@KaMok it's because we are overwriting the CSS for the specified class so it should be at the parent level so it can be overwritten.Donkey
@ShahzaibShahid You will also need to manually set the tabindex attributes of the stepper-headers to '-1' so you can't tab to the steps with a keyboardLactiferous
it works if u use ng-deep: ::ng-deep .mat-horizontal-stepper-header {...}Cutwork
Very usefull, just by passing by css, ThanksCreamcups
Yes it works unless you are using a vertical stepper in which case its ::ng-deep .mat-vertical-stepper-header{ pointer-events: none !important; }Writing
R
78

Use a linear stepper with completed=false steps. When the user presses your button, programattically complete the step and move to the next one.

This way you don't need to mess with CSS pointer events. In our app, that resulted in accessibility problems with NVDA.

    <mat-horizontal-stepper linear #stepper>
      <mat-step completed="false">
        <ng-template matStepLabel>Step 1</ng-template>
        <app-some-child (nextClicked)="nextClicked($event)" ></app-some-child>
      </mat-step>
      <mat-step>
        <ng-template matStepLabel>Step 2</ng-template>
        <app-some-other-child></app-some-other-child>
      </mat-step>
    </mat-horizontal-stepper>
    
    export class AppComponent implements OnInit {

      @ViewChild('stepper') stepper: MatStepper;

      nextClicked(event) {
        // complete the current step
        this.stepper.selected.completed = true;
        // move to next step
        this.stepper.next();
      }
    }

Roughcast answered 18/2, 2019 at 20:53 Comment(6)
This is the proper way to do it.Fraze
Cool solution! Note that it will not prevent the user to go back to first step once he is at the second step (and that may be something you need) For this you can add editable="false" to your mat-stepsAntigen
Nice solution here! Thank youNecessity
Bad solution, it doesn't disable "next" steps, previous steps don't become grayed out. Css is more right solution,Intrastate
Great answer and proper way to implementAnthracnose
Seems obvious but if you don't set the selected step as completed the stepper won't move. If you want to disable the previous step you can also set the this.stepper.selected.editable = falseLietuva
K
8

Does not work without ::ng-deep

    ::ng-deep .mat-horizontal-stepper-header{
      pointer-events: none !important;
    }
Katharinekatharsis answered 10/1, 2019 at 0:37 Comment(0)
E
8

Don't use ::ng-deep as it is deprecated.

https://angular.io/guide/component-styles#deprecated-deep--and-ng-deep

Instead, if you are using Angular Material, use the theme guide from the documentation.

https://material.angular.io/guide/theming

Example of a implementation of the style:

my-custom-elements.scss

@import '~@angular/material/theming';

@mixin custom-stepper-theme() {
  .mat-horizontal-stepper-header {
    pointer-events: none;
  }
}

global-material-theme.scss

@import '~@angular/material/theming';
// Plus imports for other components in your app.

// Include the common styles for Angular Material. We include this here so that you only
// have to load a single css file for Angular Material in your app.
// Be sure that you only ever include this mixin once!
@include mat-core();

@import './material/my-custom-elements.scss';

@include custom-stepper-theme();

angular.json

...
"styles": ["src/styles.scss", "src/app/global-material-theme.scss"]
...
Esch answered 4/10, 2019 at 19:58 Comment(2)
How can I custom for only one component is my angular app?Beckman
Put a different class on it. And use the class specification (then you can define the mixin inside the scss of that component, its a better code practice to say it is a specific style)Esch
Y
8

For anyone that's still looking for an alternate solution if ::ng-deep does not work.

Furthermore, ::ng-deep is deprecated and setting ViewEncapsulation to none is the preferred way to go if you want to do it using CSS.

Import ViewEncapsulation and set to None in your

compnent.ts:

import { Component, OnInit, ViewEncapsulation } from "@angular/core";

@Component({
  selector: "stepper-overview-example",
  templateUrl: "stepper-overview-example.html",
  styleUrls: ["stepper-overview-example.css"],
  encapsulation: ViewEncapsulation.None
})
export class StepperOverviewExample implements OnInit {
  isLinear = false;

  constructor() {}

  ngOnInit() {}
}

set pointer-events to none in your

component.css:

.mat-horizontal-stepper-header { 
  pointer-events: none !important; 
}

Here's a DEMO.

Yettayetti answered 25/10, 2019 at 8:22 Comment(2)
Thank you! I had a nested stepper and the encapuslation property you mention here made all the difference (y)Adduction
ViewEncapsulation None is not a good idea. That should be reserved for the ultimate "no other possible option" scenario. The side effects will not be obvious, and you will find them in the form of bugs over a long period of time. Lots of weird issues with styles being picked up and/or applied in all the wrong places. Just do some serious spot-checking throughout your entire app before releasing it if you chose this route.Adon
F
6

Just to improve on the accepted answer, as it will not work if you have a vertical stepper.

To stop the user being able to click the header and navigate add the following code to you style.css file in the root:-

.mat-step-header {
  pointer-events: none !important;
}

This will ensure it works for mat-horizontal-stepper-header and mat-vertical-stepper-header

Furnary answered 28/12, 2018 at 18:18 Comment(0)
P
3

I found a somewhat hacky solution for this. The problem is that you cannot fully disable the header navigation, but you can prevent it from being active until a certain moment.

And that moment is form.invalid.

My situation was the following: User needs to fill the form inside the step, press 'save' and only then be able to use NEXT button and navigate the stepper further (also back).

What I did was introduce another hidden input which would be using angular's [required] dynamic attribute. It will only be required if the previous save condition wasn't successful. Once it succeeds this field won't be required and user can navigate further.

Together with mat-stepper (or md-stepper) attribute editable you should be able to achieve what you want.

Let me know if you fully understood the idea.

Pervious answered 6/11, 2017 at 12:44 Comment(0)
O
2

This also did the trick for me

Requirement:

  • Allow navigation either from Navigation or Next Button only WHEN form is valid
  • Disabled Header navigation and keep button disabled till form is not valid.

 <mat-horizontal-stepper #stepper [linear]="true">

    <!----------------------------->
    <!---- STEP: 1:---->
    <!----------------------------->

    <mat-step #generalStep [completed]="formGroup1.valid">
      <ng-template matStepLabel>Step-1</ng-template>
      
      <form [formGroup]="formGroup1">

      // STEP-1 Content
      // matInput - with form control bindings

       <div class="container">
          <button mat-raised-button matStepperNext color="primary"[disabled]="!formGroup1.valid">Next Step</button>
        </div>

      </form>

    </mat-step>

    <!------------------------------->
    <!-- STEP: 2:-->
    <!------------------------------->


    <mat-step #generalStep [completed]="formGroup2.valid">
      <ng-template matStepLabel>Step-2</ng-template>

      <form [formGroup]="formGroup2">

      // STEP-2 Content
      // matInput - with form control bindings

      <button mat-raised-button matStepperNext color="primary"[disabled]="!formGroup.valid">Next Step</button>

      </form>
    </mat-step>

  
  </mat-horizontal-stepper>

  • FormGroup/FormControl initialization
  • Adding Validators
  formGroup1: FormGroup;
  formGroup2: FormGroup;

   this.formGroup1 = this.formBuilder.group({
      control1: new FormControl('', [Validators.required]),
      control2: new FormControl('', [Validators.required]),
    });

  this.formGroup2 = this.formBuilder.group({
    control3: new FormControl('', [Validators.required]),
    control4: new FormControl('', [Validators.required]),
  });

Outland answered 7/4, 2022 at 11:46 Comment(0)
M
2

Following works for me with click on previous step enabled:

::ng-deep .mat-vertical-stepper-header:not([ng-reflect-active="true"]){
    pointer-events: none !important;
}

For horizontal stepper do this:

::ng-deep .mat-horizontal-stepper-header:not([ng-reflect-active="true"]){
        pointer-events: none !important;
}
Myopic answered 31/5, 2022 at 17:14 Comment(0)
T
1

First you need add ViewEncapsulation.None in your component config

@Component({
 selector: 'app-example',
 encapsulation: `ViewEncapsulation.None`
 })

Then add this in your component CSS.

.mat-horizontal-stepper-header-container  {
  display: none !important;
 }
Thy answered 25/2, 2019 at 5:58 Comment(0)
B
1

I think, for now, the good way is to get the matStepper by a ViewChild and in the afterViewInit, add pointer-events style to none for each header.

@ViewChild('matStepper') matStepper: MatStepper;

...

ngAfterViewInit() {
   this.matStepper._stepHeader.forEach((x) => {
      x._elementRef.nativeElement.style.pointerEvents = 'none';
   });
}

This solution avoid not recommended ::ng-deep or ViewEncapsulation

Benzofuran answered 2/10, 2023 at 8:42 Comment(1)
that's helped me, thanksDiseuse
S
0
// so that the User cannot go directly to step 3
.mat-step-header.mat-accent[ng-reflect-index="2"] {
  pointer-events: none !important;
}

This is preventing the Users to go to step 3 from a 3 steps stepper. Change the index accordingly for your project, meaning that if you want to disable step 5, make the index equal to 4. Also, put this in some sort of a Material Overrides file, or directly in your styles.scss file.

Stillwell answered 8/8, 2023 at 13:33 Comment(0)
E
0

I used the answer from James Young and it works but navigation back is sadly not disabled. For disabling navigation back I added

this.stepper.steps.forEach((step) => (step.select = () => {}));
Ecbolic answered 11/7 at 10:54 Comment(0)
D
-1

Here ::ng-deep .mat-horizontal-stepper-header-container { display: none ; }

Use this on your style sheet to remove stepper header...Like Step-1,Step-2

Dedicate answered 22/2, 2019 at 5:51 Comment(0)
R
-4

You need to add an "linear" attribute (This will disable navigation)

<mat-vertical-stepper linear>

Ryannryazan answered 29/10, 2017 at 19:19 Comment(1)
cause error Cannot read property 'invalid' of undefined when clicking next and back buttons, or the header labels. To use linear means to add forms which is the basic idea of the stepper... but I don't need a form for each stepGhislainegholston
U
-4

In the tag add [linear]="true"

<mat-horizontal-stepper labelPostion="botton" [linear]="true">
...
</mat-horizontal-stepper>
Underlaid answered 21/1, 2019 at 17:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.