11

I use the Angular Material Tabs and need a hook to check if the user can change the tab (form is un/saved). I couldn't find any API functionality to prevent the tab change. Any ideas? Thanks

Edric
  • 24,639
  • 13
  • 81
  • 91
Lord Midi
  • 754
  • 1
  • 9
  • 25
  • You can use Disable input in MatTab to control. When form is unsaved, set the MatTab > Disable to true. You can refer to https://material.angular.io/components/tabs/api#MatTab – Yew Hong Tat Mar 07 '19 at 09:06
  • From the (given) UX flow it should be clickable and when the form is unsaved, the tab should not change and a modal overlay will be displayed with the option to save or to abort. While when clicking "abort" the tab should not change and of course as long as the user hasn't clicked "save". – Lord Midi Mar 07 '19 at 09:18
  • I think I'll go with the nav tab bar and implement the special behaviour on my own https://material.angular.io/components/tabs/overview#tabs-and-navigation – Lord Midi Mar 07 '19 at 11:34
  • https://stackoverflow.com/questions/56607394/angular-material-tab-prevent-tab-change-of-mat-tab-group-if-the-form-in-curren/56607398#56607398 – jophab Jun 15 '19 at 04:57

2 Answers2

8

I used the solution from AlessHo, however it didn't work for me that way. I had to change the function assigned to _handleClick, returning a boolean didn't do the trick.

Here's my solution:

Add @ViewChild(MatTabGroup) tabs: MatTabGroup; to the class.

Note the type parameter in place of the #tabGroup reference. It worked with it as well, but our tests didn't. tabs was undefined in the tests, but it worked with the type selector.

Next, I implemented AfterViewInit, because in the OnInit, the tab group was not reliably initialized:

ngAfterViewInit(): void {

    // Just to be sure
    if (!this.tabs) {
        throw Error('ViewChild(MatTabGroup) tabs; is not defined.');
    }

    // Returning just a boolean did nothing, but this works:

    // 1. Save the click handler for later
    const handleTabClick = this.tabs._handleClick;

    // 2. Replace the click handler with our custom function
    this.tabs._handleClick = (tab, header, index) => {

        // 3. Implement your conditional logic in canDeactivateTab()
        // (return the boolean here)
        if (this.canDeactivateTab()) {

            // 4. If the tab *should* be changed, call the 'old' click handler
            handleTabClick.apply(this.tabs, [tab, header, index]);
        }
    };
}
Emaro
  • 1,397
  • 1
  • 12
  • 21
  • I was looking for something like this, but I'm having issues on running my custom code, how would I run something after making the tabchange? – IvanS95 Jul 20 '21 at 20:11
  • I'm not sure if it works, but I'd try to do it just after step 4 `handleTabClick.apply`. – Emaro Jul 23 '21 at 10:14
1

I was facing the same issue. In my case I would not allow the user to move to another tab until he fills all mandatory details.

Finally I found something here different from (selectedTabChange)="tabChanged($event)" which is the one that has been used:

  1. Add @ViewChild('tabGroup') tabs: MatTabGroup; in your class;
  2. add #tabGroup to <mat-tab-group #tabGroup> .. </mat-tab-group> in your HTML code.
  3. Add import {MatTabGroup, MatTab, MatTabHeader } from '@angular/material'; to your class;
  4. Add this.tabs._handleClick = this.myTabChange.bind(this); in ngOnInit function;
  5. Add below function which allows tab change or not, and is able to get the tab index number:
myTabChange(tab: MatTab, tabHeader: MatTabHeader, idx: number) {    
    var result = false;    
    // here I added all checks/conditions ; if everything is Ok result is changed to true
    // ==> this way the tab change is allowed.
    return result;    
}

Cheers !

Kiran Mistry
  • 2,614
  • 3
  • 12
  • 28
Mlle 116
  • 1,149
  • 4
  • 20
  • 53
  • Should be `import { MatTabGroup, MatTab, MatTabHeader } from '@angular/material/tabs';` – LirysJH Nov 16 '21 at 10:21
  • 1
    Nice solution with two little hooks: the _handleClick(...)-function returns void! Your boolean result will be ignored. You can solve this by combining this solution with template: [selectedIndex]="selectedTabIndex" in your function: if (/* your checks here*/) { this.selectedTabIndex = idx; } Another hook is that the viewchild is accessible in ngAfterViewInit. So put the 'this.tabs._handleClick = this.myTabChange.bind(this)' into ngAfterViewInit(). that did it for me. – Robert Fornesdale Jan 13 '22 at 16:30