Angular Material Tab : Prevent tab change of mat-tab-group if the form in current tab is dirty

jophab picture jophab · Jun 15, 2019 · Viewed 9.5k times · Source

I was trying to prevent tab change of mat-tab, if the form in currently active tab is dirty.

But I couldn't find a way to intercept the tab change event.

<mat-tab-group>

  <mat-tab label="Tab 0" >

    // Tab 0 Content

  </mat-tab>

  <mat-tab label="Tab 1"  >

    // Tab 1 Content

  </mat-tab>

  <mat-tab label="Tab 2" >

    // Tab 2 Content

  </mat-tab>

</mat-tab-group>

Even though there is a selectedTabChange event, we can't prevent tab change. we can only switch tab programatically after tab change.

I have got a hack to make it possible. Just posting here to help if someone encounters the same.

Answer

jophab picture jophab · Jun 15, 2019

This solution is just a work around and has its flaws. It is mentioned below.

Steps :

In the template :

  1. Disable all tabs of the mat-tab-group <mat-tab label="Tab 0" disabled>

  2. Provide a click event handler on mat-tab-group. Also set the selectedIndex property using a variable from the component class.

    <mat-tab-group (click)="tabClick($event)" [selectedIndex]="selectedTabIndex">

In the Component class :

  1. Declare the variable selectedTabIndex
    selectedTabIndex = 0;

  2. Create a method to get the tab Index , provided the tab label.

     getTabIndex(tabName: string): number {
    
     switch (tabName) {
       case 'Tab 0': return 0;
       case 'Tab 1': return 1;
       case 'Tab 2': return 2;
       default: return -1; // return -1 if clicked text is not a tab label text
      }
    
     }
    

    We can get the tab-label text from a property of the click event

    `clickEventName.toElement.innerText`
    
  3. Create the method for handling the click event on mat-tab-group.

     tabClick(clickEvent: any) {
    
     // Get the index of clicked tab using the above function
     const clickedTabIndex = this.getTabIndex(clickEvent.toElement.innerText);
    
     // if click was not on a tab label, do nothing
     if (clickedTabIndex === -1) {
       return;
     }
    
     // if current tab is same as clicked tab, no need to change. 
     //Otherwise check whether editing is going on and decide
    
     if (!(this.selectedTabIndex === clickedTabIndex)) {
    
       if ( /*logic for form dirty check*/ ) {
    
         // if form is dirty, show a warning modal and don't change tab.
       }
       else {
    
         // if form is not dirty, change the tab
         this.selectedTabIndex = clickedTabIndex;
       }
     }
    

    }

In my case each tab content was in separate components. So I used @ViewChild to access them and check whether any of the form is dirty or not.

Flaws :

  1. Since this method uses the text of clicked element for switching tabs, there is a chance that one of your tab may contain some element with text content same as the heading of other tabs.

    So it may trigger a tab change. You can look for other properties in click event to prevent this scenario if possible. I used following code in tabClick() method's beginning

     if (!((clickEvent.toElement.className).toString().includes('mat-tab-label'))) {
      return;
      }
    
  2. You may need to override the css of disabled state of mat-tab to make it look natural

Template :

<mat-tab-group  (click)="tabClick($event)" [selectedIndex]="selectedTabIndex">

  <mat-tab label="Tab 0" disabled>

    // Tab 0 Content

  </mat-tab>

  <mat-tab label="Tab 1"  disabled>

    // Tab 1 Content

  </mat-tab>

  <mat-tab label="Tab 2"  disabled>

    // Tab 2 Content

  </mat-tab>

</mat-tab-group>