15

Using Angular 2.3.1 and ng-bootstrap 1.0.0-alpha.18. I am trying to programmatically select a tab based on ID from the component rather than from within the template. The goal is to pull the param from the url and to use that to select the tab in ngOnInit

the template

<section id="policy-terms">
<ngb-tabset>
  <ngb-tab title="Terms and Privacy" id="terms">
    <template ngbTabContent>
      <div class="container page-content">

      </div>
    </template>
  </ngb-tab>
  <ngb-tab title="Company Policy" id="policy">
    <template ngbTabContent>
      <div class="container page-content">

      </div>
    </template>
  </ngb-tab>

</ngb-tabset>
</section>

And the component code:

import { Component, OnInit } from '@angular/core';
import { NgbTabset } from '@ng-bootstrap/ng-bootstrap';

@Component({
  selector: 'app-policy-terms',
  templateUrl: './policy-terms.component.html',
  styleUrls: ['./policy-terms.component.scss'],
  providers: [
       NgbTabset
   ]
 })
 export class PolicyTermsComponent implements OnInit {

 constructor(
    public tabset: NgbTabset
  ) { }

  ngOnInit() {
    this.tabset.select('policy');
   }
}

This just produces an error:

Console log errors

How can I access this method?

bikeguy
  • 311
  • 1
  • 3
  • 10

5 Answers5

19

Routing with Ngb-TabSet

In AngularJs 1.x using ui-router setting up names routes was straight forward. In Angular 2 with Ng-Bootstrap it’s not as obvious. On the upside, what you need is available in native Angular 2 libraries.

Setting up the Route Configuration

export const appRoutes: Routes =
    [
        { path: 'prospect/:prospectid/details', component: ProspectTabsView, data:{name:'details'} },
        { path: 'prospect/:prospectid/appointments', component: ProspectTabsView, data:{name:'appointments'} },
        { path: 'prospect/:prospectid/followups', component: ProspectTabsView, data:{name:'followups'} },
        { path: 'prospect/:prospectid/contacts', component: ProspectTabsView, data:{name:'contacts'} },
        { path: '', component: DashboardView },
        { path: '**', redirectTo: '', pathMatch: 'full'}
    ];

The configuration is straightforward with one exception: the [data] attribute. You’ll notice it has a key called name. This is the name of the route. Think of it as a data attribute as a data-bag. You can add more than just the route name.

Setting up the TabSet Markup

<ngb-tabset #tabs>
    <ngb-tab id="details">
        <ng-template ngbTabTitle>
            <a [routerLink]="['/prospect', prospectId, 'details']">Details</a>
        </ng-template>
        <ng-template ngbTabContent>
        </ng-template>
    </ngb-tab>
    <ngb-tab id="contacts">
        <ng-template ngbTabTitle>
            <a [routerLink]="['/prospect',prospectId,'contacts']">Contacts</a>
        </ng-template>
        <ng-template ngbTabContent>
        </ng-template>
    </ngb-tab>
    <ngb-tab id="appointments">
        <ng-template ngbTabTitle>
            <a [routerLink]="['/prospect', prospectId, 'appointments']">Appointments</a>
        </ng-template>
        <ng-template ngbTabContent>
        </ng-template>
    </ngb-tab>
    <ngb-tab id="followups">
        <ng-template ngbTabTitle>
            <a [routerLink]="['/prospect', prospectId, 'followups']">Follow Ups</a>
        </ng-template>
        <ng-template ngbTabContent>
        </ng-template>
    </ngb-tab>
</ngb-tabset>

There is nothing magical about the above tab markup, there are couple things you want to take note of: first is in the ngb-tabset element, we’ve declared the variable #tab. We’ll use #tab later in the component. Second, each nbg-tab has an id set that matches name we defined in the route configuration (i.e. data:{name:'followups'}).

Setting up the Component

import {
     AfterViewChecked, Component, OnInit, 
    ViewChild
} from '@angular/core';
import '../../assets/css/styles.css';
import {ActivatedRoute} from "@angular/router";
import {NgbTabset} from "@ng-bootstrap/ng-bootstrap";

@Component({
    templateUrl: './tabs.view.html'
})
export class ProspectTabsView implements OnInit, AfterViewChecked{
    prospectId: number;
    selectedTab:string; 

    @ViewChild('tabs')
    private tabs:NgbTabset;

    constructor(private route: ActivatedRoute) { 
        this.route.data.subscribe(d=>{
            this.selectedTab = d.name;
        });
    }

    ngOnInit(): void {
        this.route.params.subscribe(
            params => {
                this.prospectId = +params['prospectid'];
            }
        );
    }

    ngAfterViewChecked(): void {
        if(this.tabs) {
            this.tabs.select(this.selectedTab);
        }
    } 
 }

The hardest part in this exercise was getting the order of execution correct. If it’s not correct, a collection or a property won’t be initialized before you operate on it. We’ll start at the top of the class and work our way down.

First, we have the variables. prospectId is the primary key of the data, selectedTab is the name of the currently selected tab, and lastly, we have the tabs variable. tabs is a reference to the attribute (#tab) we added to the ngb-tabset element.

Next is the constructor. It’s not obvious in the documentation, but data is an Observable<data>. To capture the value, we are subscribing to the data property from the route.

Following the constuctor is the ngOnInit. This isn’t important to the tabs, but it does capture the prospectId which we use in the tab’s routing. If you don’t have any dynamic data in your routes, then you don’t need this.

Lastly, we have ngAfterViewChecked. For routing the tabs, this is the most important. Here we use the tabs variable we captured from the markup and it’s where we pass the selected tab name to the tabs to change the selected tab.

Update

To get this working properly, I had to add to hook into the tabChange event on the ngb-tabset.

HTML:

<ngb-tabset [activeId]="selectedTab" #tabs (tabChange)="onTabChange($event)">

TypeScript:

Also, I had to hardcode the routes in the onTabChange function.

onTabChange($event: NgbTabChangeEvent) {
    let routes = {
        details: `/prospect/${this.prospectId}/details`,
        appointments: `/prospect/${this.prospectId}/appointments`,
        followups: `/prospect/${this.prospectId}/followups`,
        notes: `/prospect/${this.prospectId}/notes`,
        dials: `/prospect/${this.prospectId}/dials`,
    };

    this.router.navigateByUrl(routes[$event.nextId]);
}
Chuck Conway
  • 16,287
  • 11
  • 58
  • 101
  • Thanks, this is the approach that I was searching for my app from very long, it is very clean. However, I'm not able to get it working as I'm ending up with 'undefined' for 'this.route.data' in constructor. I'm not sure if 'this.route' is the expected route configuration object or not (couldn't verify the expected configured path in that object). – ramtech Oct 31 '17 at 10:29
  • If you are using the tab component outside of a route, meaning the component isn't directly part of a route, the route parameter will be null. You'll need to massage the code below to get it to work, but it'll give you access to the data you need. ``` // We are doing it this way, because component is not part of a // route. It lives outside of the route/activatedRoute hierarchy. this.router.events.subscribe(val => { if (val instanceof RoutesRecognized) { let q = val.state.root.firstChild.params['query']; this.searchText = q; }}); ``` – Chuck Conway Oct 31 '17 at 17:38
  • should be selected answer – sebius Apr 17 '19 at 15:25
4

This is happening because you are calling select on tabs before tabs have even been initialized. NgTabset are initialized after view has been init. I used a boolean to see if they have been initialized before calling select.

    tabsInitialized: boolean = false;
    @ViewChild('tabs') public tabs:NgbTabset;

    ngAfterViewInit() {
     this.tabsInitialized = true;
    }



    ngOnChanges(){
     if(this.tabsInitialized){
      this.tabs.select('dashboard');
    }
   }
...
  • actually I take my words back, need `#tab` on the ngb-tabsets tag so it binds to the DOM. – roger Apr 30 '18 at 03:14
4

In case anyone wants to do it from the template, the best approach is :

<ngb-tabset #tabRef="ngbTabset">
  <ngb-tab title="Tab-1" id="tab1">
     <ng-template ngbTabContent>
       <p> Tab 1 Content</p>
     </ng-template>
  </ngb-tab>
   <ngb-tab title="Tab-2" id="tab2">
     <ng-template ngbTabContent>
       <p> Tab 2 Content</p>
     </ng-template>
  </ngb-tab>
</ngb-tabset>

<div>
  <button class="btn" (click)="tabRef.select('tab2')">Select tab with id tab2</button>
</div>
Adrita Sharma
  • 21,581
  • 10
  • 69
  • 79
1

Put a reference on the element

<ngb-tabset #tabs>

Use a ViewChild to control the tab

export class PolicyTermsComponent implements OnInit {
  private tabs:NgbTabset;

  @ViewChild('tabs')  public set _tabs(tabs: NgbTabset)
  {
     if(!tabs) return;
     this.tabs = _tabs;
     this.tabs.select('policy');
  }
}

I moved the execution of the select to a set so to be sure that the page properly created the tabs component and it can be referenced and used.

misha130
  • 5,457
  • 2
  • 29
  • 51
  • I have sometghing like this: `````` ```@ViewChild('tabSet') tabset: NgbTabset; constructor ( console.info("tabset",this.tabset); ... ``` but tabset is always undefined ? – daslicht Apr 10 '17 at 11:16
0

The problem you are having is because you are executing the code in the wrong part of the event loop. You can use the await/async to push the setting of the active tab to later in the event loop with other micro-tasks

route set up .ts

// ... other stuff
{ path: ":id/:tabName", component: ViewTabsComponent },
// ... other stuff

html / markup

<ngb-tabset #myTabs>
  <ngb-tab title="Terms and Privacy" id="terms">
    <template ngbTabContent>
        <p>content</p>
    </template>
  </ngb-tab>
  <ngb-tab title="Company Policy" id="policy">
    <template ngbTabContent>
      <p>other content<\p>
    </template>
  </ngb-tab>
</ngb-tabset>

component controler ts

@ViewChild("myTabs", { static: true, read: NgbTabset }) myTabs: NgbTabset;
async ngOnInit() {
    const params = await this.route.paramMap
        .pipe(
            map((params: ParamMap) => ({ tabName: params.get("tabName") })),
            take(1) // <-- force to complete
        ).toPromise();
    this.myTabs.select(`${params.tabName}`);
}

this is working with

  • "@ng-bootstrap/ng-bootstrap": "^5.0.0",
  • "@angular/common": "^8.0.0",
Kieran
  • 17,572
  • 7
  • 45
  • 53