0

Have created a tab component that allows the user to add multiple tabs and the individual tab information will get populated upon clicking the tab header which is working fine without any issue.

I would like to control/Manage the tab click using the dropdown select options.

stackblitz

app.component.ts

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

import { TabsComponent } from './tabs/tabs.component';

@Component({
  selector: 'my-app',
  template: `
    {{ message }}
    <select
      id="select-menu-0"
      class="services-select"
      autoComplete="address-level1"
      (change)="handleDropdown($event)"
    >
    <option *ngFor="let item of options; index as i">
    {{item}}
  </option>
    </select>
    <my-tabs (onSelect)="handleTabClick($event)" [selectedIndex]="selectedTab">
      <my-tab [tabTitle]="'Tab 1'">
        Tab 1 content
      </my-tab>
      <my-tab tabTitle="Tab 2">
        Tab 2 content
      </my-tab>
      <my-tab tabTitle="Tab 3">
        Tab 3 content
      </my-tab>
    </my-tabs>
  `
})
export class AppComponent {
  message: string;
  selectedTab: number = 2;
  options: Array<string> = ['Tab 1', 'Tab 2', 'Tab 3'];

  handleTabClick(event) {
    console.log('$evnt', event);
    this.message = `${event.title} is clicked`;
  }

  handleDropdown(event) {
    console.log('EVENT', event.target.value, this.options.indexOf(event.target.value));
    this.selectedTab = this.options.indexOf(event.target.value);
    this.message = `${event.target.value} is clicked`;
    // this.handleTabClick(event);
  }
}

tabs.component.ts

import {
  Component,
  ContentChildren,
  QueryList,
  AfterContentInit,
  ViewChild,
  ComponentFactoryResolver,
  ViewContainerRef,
  Input,
  Output,
  EventEmitter
} from '@angular/core';

import { TabComponent } from './tab.component';
import { DynamicTabsDirective } from './dynamic-tabs.directive';

@Component({
  selector: 'my-tabs',
  template: `
    <ul class="nav nav-tabs">
      <li *ngFor="let tab of tabs" (click)="sendMessage(tab)" [class.active]="tab.active">
        <a href="#">{{tab.title}}</a>
      </li>
    </ul>
    <ng-content></ng-content>
  `,
  styles: [
    `
    .tab-close {
      color: gray;
      text-align: right;
      cursor: pointer;
    }
    `
  ]
})
export class TabsComponent implements AfterContentInit {

  @Input() selectedIndex: number;
  @Output() onSelect = new EventEmitter<string>();

  sendMessage(tab: Tab) {
    console.log('this.tabs', this.tabs.first, this.tabs.last);
    this.onSelect.emit(tab);
    this.tabs.toArray().forEach(tab => tab.active = false);
    tab.active = true;
  }
  
  @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
  
  // contentChildren are set
  ngAfterContentInit() {
    // get all active tabs
    console.log('selectedIndex', this.selectedIndex, typeof this.selectedIndex);
    console.log('ngAfter', this.tabs, this.tabs.toArray());
    let activeTabs = this.tabs.filter((tab)=>{ console.log('TBB', tab); return tab.active});
    console.log('activeTabs', activeTabs);

    if (this.selectedIndex) {
      console.log('SELECEELTLLT', this.tabs.toArray()[this.selectedIndex]);
      this.selectTab(this.tabs.toArray()[this.selectedIndex]);
    } else {
      this.selectTab(this.tabs.first);
    }
    
    // if there is no active tab set, activate the first
    // if(activeTabs.length === 0) {
    //   this.selectTab(this.tabs.first);
    // }
  }
  
  selectTab(tab: Tab){
    // deactivate all tabs
    console.log('tab', tab);
    this.tabs.toArray().forEach(tab => tab.active = false);
    
    // activate the tab the user has clicked on.
    tab.active = true;
  }
}

2 Answers 2

2

I assume that you want to handle the TAB change process with SELECT element, I check your code you already have implement the Parent Child Component Communication. Now You have a select dropdown in your parent component & I believe you are facing problem to handle the change event of select dropdown within your child component,

As I notice you are passing selectedIndex to your child component, Now whenever change event of select dropdown occur, within that you are also modifying the value of selectedIndex, So try to implement the OnChange interface with your child component,

modify tabs.component.ts file:

enter image description here

Now provide the implementation of OnChange interface, by defining the method like below

enter image description here

Now if you try to change select dropdown value, it will also change the tab as well,

Sign up to request clarification or add additional context in comments.

5 Comments

the problem has no relation with OnChanges
Alright! I suggest a way, you can achieve your expectation in this way as well, but may I know that, why you don't want to utilized this OnChanges?
You're correct, I thought that the problem has no relation with OnChanges, Is another aproach I`d not think, thanks for the advise -and for the lesson- (really if only has one Input is recomended use a setter, but is a good aproach too)
@SuneelKumar The solution works as expected.
@kabeerrifaye, Great!
1

I suggest another aproach: control all by the "selectedIndex"

If you defined an @Input and a @Output like

  private _selectedIndex: number;
  @Input() set selectedIndex(value) {
    if (this.tabs && this.selectedIndex != value) {
      this._selectedIndex = value;                    //equal an auxiliar variable
      const tab=this.tabs.find((_x, i) => i == value);//find the "tab"
      if (tab){
          this.selectTab(tab);                            //call to selectTab
          this.onSelect.emit(tab);                        //emit onSelect
          this.selectedIndexChange.emit(value)            //emit selectedIndexChange
      }
    }
  }
  get selectedIndex(){
    return this._selectedIndex   //simple return the variable
  }

  @Output() selectedIndexChange = new EventEmitter<number>();
  @Output() onSelect = new EventEmitter<TabComponent>();

The code becomes more simpler.

  //In the tabs.component.html just change the selectedIndex
 <li
    *ngFor="let tab of tabs;let i=index"
    (click)="this.selectedIndex=i"
    [class.active]="tab.active"
  >

  //and in tabs.component.ts
  ngAfterContentInit() {
    if (this.selectedIndex) {
      this.selectTab(this.tabs.find((_x, i) => i == this.selectedIndex));
    } else {
      this.selectTab(this.tabs.first);
    }
  }

  selectTab(tab: Tab) {
    this.tabs.forEach(x => (x.active = x == tab));
  }

And you can use in your main.app (be carefull you need import FormsModule to use [(ngModel)])

<!--simple use [(ngModel)]-->
<select [(ngModel)]="selectedTab">
    <option *ngFor="let item of options; index as i" [value]="i">
      {{item}}
    </option>
</select>

<!--see the "bannana" notation in selectedIndex-->
<my-tabs (onSelect)="handleTabClick($event)" [(selectedIndex)]="selectedTab" >
     ....
</my-tabs>

Your forked stackblitz

NOTE: See that you needn't convertToArray the QueryList, you can use the syntax this.tabs.find((x,index)=>{...})and use "index"

1 Comment

An alternative approach works and helps to reduces a couple of lines of code.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.