Material Mat-Stepper horizontal - What is the best way to place the header underneath the content?

Currently, I am utilizing the horizontal mat-stepper in a similar setup to the one demonstrated on this stackblitz: check it out here

I have a desire to switch the position of the header (where the steps are located) so that it appears below the content instead of above. While it seems like a simple task by rearranging the elements in the Elements tab within Chrome devtools, these elements remain inaccessible. Could you provide guidance on how I might achieve this adjustment? Thank you.

Answer №1

If you're looking to customize the stepper component, there isn't an official way to do it.

However, with a little bit of "hacking," you can easily modify the stepper component using an attribute directive.

Start by running

ng generate directive stepper-position
to create a new directive. Next, open up stepper-postion.directive.ts and insert the following code:

import { AfterViewInit, Directive, ElementRef, Input } from '@angular/core';

@Directive({
  selector: '[appStepperPosition]'
})
export class StepperPositionDirective implements AfterViewInit {
  @Input('appStepperPosition') position: 'top' | 'bottom';
  element: any;

  constructor(private elementRef: ElementRef) {
    this.element = elementRef.nativeElement;
  }

  ngAfterViewInit(): void {
    if (this.position === 'bottom') {
      const header = this.element.children[0];
      const content = this.element.children[1];
      this.element.insertBefore(content, header);
    }
  }
}

Lastly, navigate to the HTML template where your mat-horizontal-stepper is defined and include the attribute

appStepperPosition="bottom"
.

For example:

<mat-horizontal-stepper appStepperPosition="bottom" [linear]="isLinear" #stepper>

Now, your stepper content will appear above the header! 😄

Answer №2

To implement a customized stepper component, you will need to create a new one tailored to your specific requirements. In my case, I needed a vertical stepper that could display a summary of completed steps. Fortunately, all the necessary code for this custom component is available on GitHub.

Start by copying a few essential files from https://github.com/angular/components/tree/master/src/material/stepper into a designated folder in your project:

  • stepper-horizontal.html
  • stepper.scss

After importing these files, proceed to create a new custom stepper component based on the new layout. Let's name it CustomHorizontalStepper.

custom-horizontal-stepper.ts
// Importing required modules and components
import { MatStepper, matStepperAnimations } from "@angular/material";

// Defining the properties and functionalities of CustomHorizontalStepper component 
@Component({
  selector: 'custom-horizontal-stepper',
  exportAs: 'customHorizontalStepper',
  templateUrl: 'stepper-horizontal.html',
  styleUrls: ['stepper.css'],
  inputs: ['selectedIndex'],
  host: {
    'class': 'mat-stepper-horizontal',
    '[class.mat-stepper-label-position-end]': 'labelPosition == "end"',
    '[class.mat-stepper-label-position-bottom]': 'labelPosition == "bottom"',
    'aria-orientation': 'horizontal',
    'role': 'tablist',
  },
  animations: [matStepperAnimations.horizontalStepTransition],
  // Setting up providers and configurations
  providers: [
    {provide: MatStepper, useExisting: CustomHorizontalStepper},
    {provide: CdkStepper, useExisting: CustomHorizontalStepper}
  ],
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomHorizontalStepper extends MatStepper {
  /** Specify whether the label should be displayed at the bottom or end */
  @Input()
  labelPosition: 'bottom' | 'end' = 'end';

  static ngAcceptInputType_editable: BooleanInput;
  static ngAcceptInputType_optional: BooleanInput;
  static ngAcceptInputType_completed: BooleanInput;
  static ngAcceptInputType_hasError: BooleanInput;
}

Next, make alterations to the stepper-horizontal.html file as specified below:

stepper-horizontal.html
<div class="mat-horizontal-content-container">
  <div *ngFor="let step of steps; let i = index"
       class="mat-horizontal-stepper-content" role="tabpanel"
       [@stepTransition]="_getAnimationDirection(i)"
       (@stepTransition.done)="_animationDone.next($event)"
       [id]="_getStepContentId(i)"
       [attr.aria-labelledby]="_getStepLabelId(i)"
       [attr.aria-expanded]="selectedIndex === i">
    <ng-container [ngTemplateOutlet]="step.content"></ng-container>
  </div>
</div>

<div class="mat-horizontal-stepper-header-container">
  <ng-container *ngFor="let step of steps; let i = index; let isLast = last">
    <mat-step-header class="mat-horizontal-stepper-header"
                     (click)="step.select()"
                     (keydown)="_onKeydown($event)"
                     [tabIndex]="_getFocusIndex() === i ? 0 : -1"
                     [id]="_getStepLabelId(i)"
                     // Include other relevant attributes here
    </mat-step-header>
    <div *ngIf="!isLast" class="mat-stepper-horizontal-line"></div>
  </ng-container>
</div>

Once you have adjusted the HTML template, remember to register your custom component in the module and utilize it in your application just like the standard stepper component, but using the newly defined selector.

<custom-horizontal-stepper></custom-horizontal-stepper>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Typescript is struggling to accurately infer extended types in some cases

My goal is to optimize the body of a NextApiRequest for TypeScript. I currently have this code snippet: // This is a type from a library I've imported export interface NextApiRequest { query: Partial<{ [key: string]: string | string[]; ...

How can the Angular Reactive Forms be used to update the form model with a value different from what is displayed in the input/control field?

I need to handle a date input in a form. Users can enter the date in the format "DD MM YYYY" such as "25 12 2021". However, I want to store this value in a different format in the form data/model: "2021-12-25". This means that while users see one format ...

When incorporating Dragular into an Angular 2 application, an error may occur if the document object

Being new to Angular2, I encountered challenges while trying to integrate Dragula into my application. Upon running the app, an error occurred before loading the home page: Error: Node module call failed with message: Prerendering failed due to ReferenceE ...

Addressing important problems post npm audit

I recently encountered some issues after running the npm audit command on a project that was just updated to angular 15. Upon inspection, the npm audit flagged a critical vulnerability related to hermes-engine with a fix available through the 'npm au ...

Path for WebStorm file watcher's output

I'm in the process of setting up a Typescript project in WebStorm, where I want the files to be transpiled into a dist folder. This is my current project folder structure: projectroot/src/subfolder/subfolder/index.ts What I aim for is to have the f ...

Is it possible to display Angular Material Slider after the label?

Searching through the Angular Material docks, I came across the Sliders feature. By default, the slider is displayed first, followed by its label like this: https://i.sstatic.net/C5LDj.png However, my goal is to have the text 'Auto Approve?' sh ...

Understanding authorization and user roles within an Angular 2 application utilizing Microsoft Graph

As a newcomer, I am in the process of developing a CVThèque application using angular 2 and .net core. For authentication purposes, I have integrated Microsoft Graph and adal successfully. However, I am unsure how to set up permissions and group roles for ...

Directly mapping packages to Typescript source code in the package.json files of a monorepo

How can I properly configure the package.json file in an npm monorepo to ensure that locally referenced packages resolve directly to their .ts files for IDE and build tooling compatibility (such as vscode, tsx, ts-node, vite, jest, tsc, etc.)? I want to a ...

The Angular CDK Selection Model presents an issue regarding the status of checkbox headers when using a paginated table

Currently, I am working on implementing a paginated table feature with row selection using the angular CDK SelectionModel collection. In my sample scenario, I have set up a basic example where I can switch between a couple of pages of data in the table. H ...

Is There a Comparable Feature to *ngIf in DevExtreme?

Currently, I am diving into the world of webapp development using DevExtreme. As a novice in coding, this is my first time exploring the functionalities of DevExtreme. Essentially, I am seeking guidance on how to display certain elements based on specific ...

The object provided as the "filter" parameter for the find() function is required to be an object

Hey all, I'm encountering this error in my MEAN STACK project: Failed to load resource: the server responded with a status of 500 (Internal Server Error) Parameter "filter" to find() must be an object, got 1984 Here is the backend code snippet: ...

Experiencing unexpected behavior with Next.JS getStaticProps functionality

I am currently working on a website where I want to display dynamic feedback as cards. However, my fetchData variable within the Home function is always returning undefined. Here's the code snippet I have tried: import UserCard from "../component ...

Exploring Angular 10 Formly: How to Retrieve a Field's Value within a Personalized Formly Wrapper

Utilizing Angular 10, I have a formly-form with a select-field named session. This select field provides options from which to choose a dndSession. Each option holds key-value pairs within an object. I want to add a button next to the select-field that tr ...

Information sent from TypeScript frontend to Java backend is converted into a LinkedHashMap

I have a situation where my typescript frontend is communicating with my java backend using REST. Recently, I added a new simple rest endpoint but encountered an issue when trying to cast the sent object properly because the body being sent is a LinkedHash ...

How can angular/typescript be used to convert a percentage value, such as 75.8%, into a number like 75.8?

I have obtained a value (for example, "75.8%") as a string from an API and I need to convert it to a number in order to apply conditions. For instance: <div class="container" [ngClass]="{ 'pos' : value > 0, ...

Adding Components Dynamically to Angular Parent Dashboard: A Step-by-Step Guide

I have a dynamic dashboard of cards that I created using the ng generate @angular/material:material-dashboard command. The cards in the dashboard are structured like this: <div class="grid-container"> <h1 class="mat-h1">Dashboard</h1> ...

What is the best way to utilize the fresh Sanitizer API in Typescript?

Everything seems to be working well on Codepen, even without using window. It's surprising because I'm used to having to use window.x if ( 'Sanitizer' in window ) { console.log( 'sani', 'Sanitizer' in window ); } ...

The module '@material-ui/lab' could not be located

I'm currently developing a front-end page that requires the use of @material-ui/lab. I encountered an issue after installing the package using npm, where I received a typescript error during compilation: TS2307: Cannot find module '@material-ui/l ...

Utilizing the real module instead of resorting to mock usage

I've configured Jest as follows: jest: { configure: { testEnvironment: 'jsdom', preset: 'ts-jest', transform: {...}, moduleNameMapper: { antd: '<rootDir>/__mocks__/antd/index.tsx&apo ...

There seems to be an issue as the function Response.cookie is

I am a newcomer to using NestJS and currently utilizing it to manage a REST API server. My goal is to send some HTTP-only cookies in the response, so I referred to the official documentation for guidance. The documentation suggests using the cookie method ...