Is there a way to safeguard against accidental modifications to MatTab without prior authorization?

I need to delay the changing of the MatTab until a confirmation is provided. I am using MatDialog for this confirmation. The problem is that the tab switches before the user clicks "Yes" on the confirmation dialog.

For instance, when I try to switch from the income tab to the adjustment tab, I want the confirmation popup to appear first. However, currently, the popup only shows up after I have already moved to the adjustment tab.

https://i.sstatic.net/YVeiH.png

This is my component template:

<mat-tab-group (click)="tabClick($event)">
  <mat-tab *ngFor="let tab of tabs; let index = index" [label]="tab">
    <app-spread></app-spread>
  </mat-tab>
</mat-tab-group>

This is the relevant method in my component ts file (onClick's method):

tabClick(clickEvent: any) {
    if (clickEvent.target.innerText != 'First') {
      this.confirm();
    }
  }
  
  public async confirm() {
    const dialogRef = this.dialog.open(ConfirmationDialogComponent, {
      maxHeight: '200px',
      maxWidth: '767px',
      width: '360px',
      disableClose: true,
      data: {
        title: 'Confirmation Message',
        content:
          'There are valid statements that are not Final. Set the statements as Final?'
      }
    });
    
    const res = dialogRef.afterClosed().subscribe(result => {
      if (result === 1) {
        //TODO need to change the tab
      } else {
        //TODO no need to change the tab
      }
    });
  }

Answer №1

A while back, I experimented with creating a "mat-tab" in this Stack Overflow thread

I believe that you can easily adapt this by simply modifying the function

<mat-tab-group #tabgroup style="margin-bottom:5px;" 
               animationDuration="0"   mat-align-tabs="start"
               (selectedIndexChange)="change(tabgroup,$event)">
...
</mat-tab-group>

Here is the modified function called 'change':

  change(tab:any,index:number)
  {
    if (tab.selectedIndex!=this.indexOld){
      const dialogRef = this.dialog.open(....)
      const res = dialogRef.afterClosed().subscribe(result => {
        if (result === 1) {
           this.index=index;
        } else {
           tab.selectedIndex=this.indexOld;
        }
      });
    }
    else
    {
       this.index=index;
    }
  }

You can view the code in action on StackBlitz where I have used a confirm dialog.

Update: There may be issues when using a mat-dialog.

Another approach involves overriding the behavior of the mat-tab-group

We need to adjust the _handleClick function of the mat-tab-group, (see this function in github) and the function _handleKeydown of the mat-header (see this function in github

It's a bit complex, but all we need to do is obtain the mat-group using ViewChild (I use {static:true} because my tabgroup is always visible).

@ViewChild('tabgroup', { static: true }) tabgroup: MatTabGroup;

Then, in the ngOnInit (if not using static:true, we use ngAfterViewInit), we re-implement the function by first casting this.tabgroup to any type

Firstly, let's define a function called alertDialog

  alertDialog(index:number,tab:MatTab,tabHeader:any,)
  {
    const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
      data: {},
    });
    const res = dialogRef.afterClosed().subscribe((result) => {
      if (result) {
        tabHeader.focusIndex = index;

        if (!tab.disabled) {
          this.tabgroup.selectedIndex = index;
        }
      }
    });

  }

Then, in ngOnInit we write

ngOnInit(){
    (this.tabgroup as any)._handleClick = (
      tab: MatTab,
      tabHeader: any,
      index: number
    ) => {
      if (this.tabgroup.selectedIndex != index)
        this.alertDialog(index, tab, tabHeader);
    };
}

To override the _handleKeydown function, wrap everything in a setTimeout to allow Angular time to render the mat-tab-header

  ngOnInit() {
    ....
    setTimeout(() => {
      const tabHeader = (this.tabgroup as any)._tabHeader;
      tabHeader._handleKeydown = (event: KeyboardEvent) => {
        if (hasModifierKey(event)) {
          return;
        }

        switch (event.keyCode) {
          case ENTER:
          case SPACE:
            if (tabHeader.focusIndex !== tabHeader.selectedIndex) {
              const item = tabHeader._items.get(tabHeader.focusIndex);
              this.alertDialog(tabHeader.focusIndex, item, tabHeader);
            }
            break;
          default:
            tabHeader._keyManager.onKeydown(event);
        }
      };
    });

You can view this implementation on a new StackBlitz page

Answer №2

Following the latest update of the answer, have you considered implementing a Directive?

@Directive({
  selector:'mat-tab-group',
})
export class MatTabGroupExtends implements OnInit{
  @Output() beforeChange:EventEmitter<any>=new EventEmitter<any;>()

  constructor(private tabgroup:MatTabGroup) {}
  ngOnInit()
  {
    if (this.beforeChange.observers.length && this.tabgroup)
    {
      setTimeout(() => {
        const tabHeader = (this.tabgroup as any)._tabHeader;
        tabHeader._handleKeydown = (event: KeyboardEvent) => {
          if (hasModifierKey(event)) {
            return;
          }

          switch (event.keyCode) {
            case ENTER:
            case SPACE:
              if (tabHeader.focusIndex !== tabHeader.selectedIndex) {
                const item = tabHeader._items.get(tabHeader.focusIndex);
                if (!item.disabled)
                this.beforeChange.emit(tabHeader.focusIndex);
              }
              break;
            default:
              tabHeader._keyManager.onKeydown(event);
          }
        };
      });

      (this.tabgroup as any)._handleClick = (
        tab: MatTab,
        tabHeader: any,
        index: number
      ) => {
        if (this.tabgroup.selectedIndex != index && !tab.disabled)
        this.beforeChange.emit(index);
      };
    }
  }
}

Given that the selector is mat-tab-group, we can simplify its usage like so:

<!-- Note that "event" refers to the "index", and 
                 "tabgroup" is the template reference variable -->
<mat-tab-group #tabgroup (beforeChange)="alertDialog($event,tabgroup)">
   ...
</mat-tab-group>

 alertDialog(index:any,tabgroup:MatTabGroup) {
    const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
      data: {},
    });
    const res = dialogRef.afterClosed().subscribe((result) => {
      if (result) {
        tabgroup.selectedIndex=index
      }
    });
  }

Answer №3

My response is inspired by Eliseo's exceptional solutions with some slight modifications, eliminating the need for setTimeout and reducing the use of ANY to a minimum.

import { AfterViewInit, Directive, EventEmitter, Output } from '@angular/core';
import { MatTab, MatTabGroup } from '@angular/material/tabs';
import { ENTER, hasModifierKey, SPACE } from '@angular/cdk/keycodes';


@Directive({
  selector: 'mat-tab-group[matTabGroupWithOuterChangeHandler]',
})
export class MatTabGroupWithOuterChangeHandlerDirective implements AfterViewInit {
  @Output() outerChangeTabHandler: EventEmitter<number> = new EventEmitter<number>();

  constructor(private tabGroup: MatTabGroup) {}

  ngAfterViewInit(): void {
    this.setCustomHandleKeyPress();
    this.setCustomHandleClick();
  }

  private setCustomHandleKeyPress(): void {
    const tabHeader = (this.tabGroup as any)._tabHeader;
    tabHeader._handleKeydown = (event: KeyboardEvent) => {
      if (hasModifierKey(event)) {
        return;
      }

      switch (event.keyCode) {
        case ENTER:
        case SPACE:
          if (tabHeader.focusIndex !== tabHeader.selectedIndex) {
            const item = tabHeader._items.get(tabHeader.focusIndex);
            if (!item.disabled) {
              this.outerChangeTabHandler.emit(tabHeader.focusIndex);
            }
          }
          break;
        default:
          tabHeader._keyManager.onKeydown(event);
      }
    };
  }

  private setCustomHandleClick(): void {
    (this.tabGroup as any)._handleClick = (tab: MatTab, _, index: number) => {
      if (this.tabGroup.selectedIndex !== index && !tab.disabled) {
        this.outerChangeTabHandler.emit(index);
      }
    };
  }
}

html

<mat-tab-group 
    matTabGroupWithOuterChangeHandler
    [(selectedIndex)]="selectedIndex"
    (outerChangeTabHandler)="showLoseChangesModal($event)">
   ...
</mat-tab-group>

ts

public showLoseChangesModal(index:number) {
    const dialogRef = this.dialog.open(DialogOverviewExampleDialog, {
      data: {},
    });
    dialogRef.afterClosed().subscribe((ok) => {
      if (ok) {
        this.selectedIndex=index;
        this.cdr.detectChanges(); // if OnPush strategy
      }
    });
  }

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

In order for the expansion parameter to be successfully used, it must be either of tuple type or passed to the

const myFunction = function(arg1: number, arg2: number, arg3: number) {} const myFunction1 = function() {} const obj = { myFunction, myFunction1 }; type FuncType = typeof obj; function executeFunction<T extends keyof FuncType>(key: ...

Implementing conditional requirements using *ngIf in Angular: A step-by-step guide

<div class="p-field-checkbox"> <p-checkbox formControlName="passwordUpdate" binary="false" name="passwordUpdate" inputId="passwordUpdate"></p-checkbox> <label for="password ...

What could be causing this error in a new Next.js application?

After multiple attempts, my frustration and disappointment in myself are causing a headache. Can someone please assist me? I am trying to create an app using the following command: npx create-next-app@latest --ts Immediately after running next dev, I enco ...

Implementing TypeScript with react-router-dom v6 and using withRouter for class components

Trying to migrate my TypeScript React app to use react-router-dom version v6, but facing challenges. The official react-router-dom documentation mentions: When upgrading to v5.1, it's advised to replace all instances of withRouter with hooks. Howe ...

Integrate a service component into another service component by utilizing module exports

After diving into the nestjs docs and exploring hierarchical injection, I found myself struggling to properly implement it within my project. Currently, I have two crucial modules at play. AuthModule is responsible for importing the UserModule, which conta ...

When using the map function, I am receiving an empty item instead of the intended item based on a condition

Need assistance with my Reducer in ngRx. I am trying to create a single item from an item matching an if condition, but only getting an empty item. Can someone please help me out? This is the code for the Reducer: on(rawSignalsActions.changeRangeSchema, ...

Is it possible to include a submenu within a md-tooltip?

I have set up an md-tooltip for a sidebar, but I would like to enhance it by enabling a submenu option. For instance, if there is a Contacts submenu under the Profile menu, I want to be able to access Contacts when hovering over the Menu Icon. The tooltip ...

What is the best way to sort an array based on a person's name?

I have a list of different groups and their members: [ { "label": "Group A", "fields": [ { "value": "color1", "name": "Mike" }, { &quo ...

Tips for efficiently verifying existence of object attribute in conditional object type using Typescript

Below is a simplified version of the code I am working with, showcasing a type that can be an interface or another: interface ChatBase { roomId?: string type: "message" | "emoji" configs: unknown } interface ChatMessage exte ...

Utilizing Async/Await with Node.js for Seamless MySQL Integration

I have encountered two main issues. Implementing Async/Await in database queries Handling environment variables in pool configurations I am currently using TypeScript with Node.js and Express, and I have installed promise-mysql. However, I am open to usi ...

"Building a tree structure from a one-dimensional array in Angular: A step-by-step

I would like to implement a tree structure in Angular using flat array data and I am willing to explore different packages for rendering the tree. Users should be able to click on a node to view details such as node ID and title. The tree should initially ...

Adjust the size of an event in the Angular Full Calendar with Chronofy, while utilizing constraints to control the drag and drop functionality

I'm currently in the process of developing an availability calendar for scheduling meetings during open times. If a time slot is unavailable, users should not be able to place an event there. While I have successfully implemented this feature, I am ...

Definition file for Typescript d.ts that includes optional properties in a function

Within my code, I have a function that offers different results based on specified options. These options dictate the type of return value. The function is currently written in plain JavaScript and I am looking to provide it with types using an index.d.ts ...

Attempting to verify if a function is being invoked within a subscription, but finding that it never gets activated

I have a method that needs to be tested when called in an ngOnInit. Specifically, I want to ensure that when the ngOnInit is triggered, this.anotherService.methodToCall() is executed and verify what parameters it is called with. However, despite my effort ...

Understanding how to leverage styles.module.scss for implementing personalized styling within react-big-calendar

I'm currently working with the react-big-calendar library in order to develop a customized calendar. However, I've encountered an issue where the SCSS styling is not being applied correctly. export const JobnsCalendar = () => ( <Calendar ...

Implement the addition of subcollections to a unified identification document in Firestore

I am struggling to store the URLs of multiple images (previously saved in Storage) in a subcollection of a Firestore document. Although I have managed to do it, each image generates a new document with its corresponding sub collection img, which is not my ...

Angular - Navigate to Login Page post registration and display a confirmation message

As a newcomer to Angular, I am currently working on an Angular and Spring Boot application. So far, I have created components for user login and registration along with validation features. Now, my goal is to redirect the user to the login page upon succes ...

Angular - encountering challenges when implementing the native Web Speech API

My goal is to integrate the native Web Speech API directly without relying on any third-party libraries. I have successfully integrated the API into my service and voice recognition is functioning properly, able to generate text from recognized speech. Ho ...

Can you provide information on the latest stable release of animations for Angular 4?

Encountering an error during npm install Warning - @angular/[email protected] requires a peer of @angular/[email protected] but none was installed. Error encountered during npm start Issue with node_modules/@angular/platform-browser/animations ...

Creating mock objects with Jest

I am currently delving into the world of jest testing. Here is a snippet from an implementation class I'm working with: import { ExternalObject } from 'external-library'; export class MyClass { public createInstance(settings : ISettings) ...