[FIXED] How to define static index for tabPanel

Issue

I’m using PrimeNG tabView component to display steps, let’s say i have 3 steps [1,2,3], and i’m using handleTabViewChange(e) to load data with the index of the step

The corresponding index is [0,1,2], step 2 is displayed with a condition, when it’s not displayed i have 2 steps [1,3] with index [0,1], so the index of step 3 changes to [1] :

Is there a way to set a static index for each tab, so when step 2 is not displayed the index of step 3 stays [2]:

<p-tabView (onChange)="handleTabViewChange($event)">
    <p-tabPanel header="Step 1" >

    </p-tabPanel>
    <p-tabPanel header="Step 2" *ngIf="false" >
        
    </p-tabPanel>
    <p-tabPanel header="Step 3" >
        
    </p-tabPanel>
</p-tabView>

handleTabViewChange(e):

  data: any[];

  handleTabViewChange(e) {    
    var step= e.index + 1;
    this.data = this.loadData(step);
  }

Solution

You can do this by using a directive which adds a static index property to the panels, and by using ViewChildren to enumerate the rendered panels:

Directive:

@Directive({
  selector: 'p-tabPanel'
})
export class MyTabPanelDirective {
  @Input() index: number;

  constructor() {}
}

The tab view in your template should look something like this:

<p-tabView (onChange)="handleTabViewChange($event)">
    <p-tabPanel header="Step 1" [index]="0">

    </p-tabPanel>
    <p-tabPanel header="Step 2" [index]="1" *ngIf="false" >
        
    </p-tabPanel>
    <p-tabPanel header="Step 3" [index]="2" >
        
    </p-tabPanel>
</p-tabView>

And finally your component can handle the onChange like this:

  @ViewChildren(MyTabPanelDirective) panels: QueryList<MyTabPanelDirective>;

  handleTabViewChange(e) {
    const step = this.panels.get(e.index + 1).index;
    this.data = this.loadData(step);
  }

The way this works is that the event argument includes the index of the panel in the DOM, and this matches the view children query list (with a caveat, see below). Then in the event handler we take the DOM index, add 1, and extract the static index from the directive.

CAVEAT 1 if you manipulate the tab panels in code, by adding or removing or reordering them, then the query list order might not match the DOM order.

CAVEAT 2 if you have two tab views in the same template, you have to change this a bit.

Solution for caveat #2

Multiple tab views

Use a service provided by the tab view (so each tab view has its own instance) – and inject it into the panels. Every panel will then register itself with the service, and this will happen according to the order of the DOM (at least initially, see caveat #1).

Then in the event handler, query the tab view’s service for the panel by index.

Service:

@Injectable({ providedIn: 'root' })
export class TabRepoService {
  panels: MyTabPanelDirective[] = [];

  constructor() {}

  addPanel(panel: MyTabPanelDirective) {
    this.panels.push(panel);
  }
}

The tab view directive attaches to all tab views and creates a service instance for itself and its children (the panels):

@Directive({
  selector: 'p-tabView',
  exportAs: 'myTabView',

  // This means there will be a separate instance per tab view
  providers: [TabRepoService] 
})
export class MyTabViewDirective {
  constructor(public repo: TabRepoService) {}

  getPanelAtIndex(i: number) {
    return this.repo.panels[i];
  }
}

Next, modify the panel directive to use this service to register themselves:

@Directive({
  selector: 'p-tabPanel',
})
export class MyTabPanelDirective {
  @Input() index: number;

  constructor(private repo: TabRepoService) {
    repo.addPanel(this);
  }
}

The html usage:

<p-tabView #tab1="myTabView" (onChange)="handleTabViewChange($event, tab1)">
  <p-tabPanel header="Header 1" [index]="0">
    Content 1
  </p-tabPanel>
  <p-tabPanel header="Header 2" [index]="1" *ngIf="false">
    Content 2
  </p-tabPanel>
  <p-tabPanel header="Header 3" [index]="2">
    Content 3
  </p-tabPanel>
</p-tabView>

Note that we pass the tab view directive to the event handler, so that it can query it for it’s child by index.

And finally the event handler:

  handleTabViewChange(e, t: MyTabViewDirective) {
    const step = t.getPanelAtIndex(e.index).index;
    this.data = this.loadData(step);
  }

Note: In this solution ViewChildren is not required.

Example StackBlitz

Answered By – Aviad P.

Answer Checked By – Cary Denson (Easybugfix Admin)

Leave a Reply

(*) Required, Your email will not be published