(tabset, feature): Ability to control active tab via activeId or some @Input property

Feature description:

Currently, to change active tab programmatically we have to call NgbTabset‘s select() method. activeId property does not work for this purpose.

This causes issues in certain scenarios. For example, in a “truly” reactive app, we have to not only manually subscribe() to update the active tab instead of using data binding, we also have to call select() on the next JS event cycle (see the demo below). I’m not sure about the latter, but the former makes API not very reactive and Angular-friendly.

Also, we have to write extra code in a component (and in some cases in a template as well) just to get access to NgbTabset instance like this:

  @ViewChild(NgbTabset)
  private tabset: NgbTabset;

Conversely, Material MatTabGroup has this feature (selectedIndex property) and could be a good example here.

Link to minimally-working plunker that reproduces the issue:

Stackblitz

Version of Angular, ng-bootstrap, and Bootstrap:

Angular: 6.0.2

ng-bootstrap: 2.0.0

Bootstrap: 4.1.1

2 thoughts on “(tabset, feature): Ability to control active tab via activeId or some @Input property

  1. Using data binding for this types of APIs has one fundamental issue in Angular – it doesn’t allow you to select the same tab twice. Let me explain the scenario:

    • data binding for activeId changes to, say tab-1;
    • user clicks on another tab (so now active tab is, say tab-3;
    • you want to select tab-1 again – BAM – it doesn’t work since Angular change detection works on binding level and for Angular value of activeId didn’t change (internal model derived from activeId changed but not the activeId binding itself!

    We could use 2-way binding ([(activeId)]="exp" but it would have following drawbacks:

    • one couldn’t use “static” values (ex. atvieId="my-tab";
    • users would be getting wired errors when not using 2-way data binding (ex. [activeId]="exp")

    Explicit methods like select(...) is the only way I know to solve it elegantly. Happy to discuss other options / other API ideas, if you’ve got suggestions.

  2. You can actually achieve what you want to do in a much more simple way 👍

    Here is a stackblitz: https://ng-bootstrap-tabset-reactive-tab-select-phepks.stackblitz.io

    There is no need here to use changeDetectorRef, nor applicationRef.
    You just need to do 3 things (step 2 is not even an action!) :

    1. Make sure to create the proper data structure associated to the tab you want to generate
    2. Wait for changeDetection to be executed, so that the ngFor on the tabset is executed.
    3. Select the proper tab

    In order to properly achieve the transition from step 2 to step 3 you need to execute the this.tabset.select( ... ) after the changeDetection.
    To do that, you could:

    • Use setTimeout(... , 0) which basically will execute the callback beginning of next tick
    • Use zone.onStable that hooks itself at the end of the microtask execution, ie. just before going to next tick.

    Personally I would rather prefer the second one using zone as it is much clearer to me as compare to the magical fallback of setTimeout. It’s definitely more Angular oriented, hence more verbose also…