16

I display several tabs with a loop ngFor using Angular Material tabs. This works properly however now I would like to open the second tab when initialised and not the first tab. Therefore, I add the property selectedIndex in the mat-tab-group but it doesn't work and still continues to open on the first tab.

HTML

<mat-tab-group class="col-10 offset-1" (selectedTabChange)="onTabChanged($event)" [selectedIndex]="1">
  <mat-tab label={{tab.name}} *ngFor="let tab of tabs; let index = index">
    <div>
      Values: {{tab.values}}
    </div>
  </mat-tab>
</mat-tab-group>

The variable "tabs" is fetched with a service from the server in ngOnInit like this:

COMPONENT

this.api.getDataset(this.route.snapshot.params["experimentid"]).subscribe(
  res => {
    this.tabs = res;
  },
  err => {
    console.log(err);
  }
);

I think it comes from here because if I set the tabs manually without requesting to the server it works. Like this:

this.tabs = [{name: "X2", values: "52"}, {name: "Z2", values: "52"}, {name: "R2", values: "52"}]
PierBJX
  • 2,093
  • 5
  • 19
  • 50
  • 4
    Try adding `*ngIf="tabs && tabs.length"` to your `mat-tab-group` component. See also example https://ng-run.com/edit/OxHxsmOQUgfxuH0grSra – yurzui Jul 14 '18 at 19:12
  • @yurzui a much better solution than mine. It might not work if changing the tabs array from one set to another set. – Reactgular Jul 14 '18 at 19:33

2 Answers2

19

You can set the selectedIndex after your service data is available. You need to do below changes:

  1. Get hold of a reference to MatTabGroup instance in your component (the component whose template contains mat-tab-group) by declaring below attribute:

    @ViewChild(MatTabGroup) tabGroup: MatTabGroup;
    
  2. Then set the selectedIndex in subscribe method call while updating new value for tabs

    .subscribe(
      res => {
        this.tabs = res;
        this.tabGroup.selectedIndex = 1;
      },
    

Overall, you component may look somewhat like below:

import {Component, OnInit, ViewChild } from '@angular/core';
import { MatTabGroup } from '@angular/material';

@Component({
  selector: 'tab-group-basic-example',
  templateUrl: 'tab-group-basic-example.html',
  styleUrls: ['tab-group-basic-example.css'],
})
export class TabGroupBasicExample implements OnInit {

  @ViewChild(MatTabGroup) tabGroup: MatTabGroup;

  tabs = [];

  ngOnInit() {
    this.api.getDataset(experimentId).subscribe(
      res => {
          this.tabs = res;
          this.tabGroup.selectedIndex = 1;
      },
      err => {
          console.log(err);
      }
    );
  }
}
Wand Maker
  • 18,476
  • 8
  • 53
  • 87
  • 2
    I didn't answer it this way, because I thought `this.tabGroup.selectedIndex` would not trigger change detection inside the component. If you look at the source code in github the setter function does not trigger an update. https://github.com/angular/material2/blob/master/src/lib/tabs/tab-group.ts#L110 – Reactgular Jul 14 '18 at 21:12
14

<mat-tab-group> only reads the [selectedIndex] input when the component is first created. Afterwards the output binding (selectedIndex) lets you track tab changes.

I think you're setting the value 1 before that tab exists.

The problem is that *ngFor is creating the children mat-tab components afterwards. When <mat-tab-group> is notified that the children have changed it defaults to the first tab.

The only work-around that I know of is to trigger a change to the [selectedIndex] binding after the child <mat-tab> components have been created.

Update your component to have a property:

public selectedIndex: number = 0;

Use this as the binding for the selectedIndex input:

<mat-tab-group class="col-10 offset-1" 
               (selectedTabChange)="onTabChanged($event)" 
               [selectedIndex]="selectedIndex">

Inject these dependencies into your component:

  public constructor(private change: ChangeDetectorRef) {}

Update your subscribe to change the selected index after the *ngFor has finished updating the document.

this.api.getDataset(this.route.snapshot.params["experimentid"]).subscribe(res => {
    this.tabs = res;
    // wait for ngFor to finish
    window.setTimeout(()=>{
       this.selectedIndex = 1;
       this.change.markForCheck();
    });
});
Reactgular
  • 52,335
  • 19
  • 158
  • 208