Developing a Tab component

杨旭 bio photo By 杨旭

Developing a Tab component

  • The consumer should use it like this:
<tabs>
  <tab tabTitle="Tab 1">
    Here's some content.
  </tab>
  <tab tabTitle="Tab 2">
    And here's more in another tab.
  </tab>
</tabs>

Create tabs component

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

@Component({
  selector: 'my-tabs',
  templateUrl: './tabs.component.html',
  styleUrls: ['./tabs.component.scss']
})
export class TabsComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }
}
<ul>
  <li>Tab 1</li>
  <li>Tab 2</li>
</ul>
  • Use the component with my-tabs tag
<my-tabs></my-tabs>

We need a place to show the tab content with ng-content

  • Add ng-content in the template
<ul>
  <li>Tab 1</li>
  <li>Tab 2</li>
</ul>
<ng-content></ng-content>
  • When using the component, put tab contents in <tabs> tag
<my-tabs>
  <p>Tab content</p>
</my-tabs>

Create a tab element to replace <li>Tab 1</li> with the property tabTitle

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

@Component({
  selector: 'my-tab',
  templateUrl: './tab.component.html',
  styleUrls: ['./tab.component.scss']
})
export class TabComponent implements OnInit {

  @Input() tabTitle: string;

  constructor() { }

  ngOnInit() {
  }

}
<div>
  <ng-content></ng-content>
</div>
  • Use the component like this:
<my-tabs>
  <my-tab tabTitle="tab1">
    Tab 1 content
  </my-tab>
  <my-tab tabTitle="tab2">
    Tab 2 content
  </my-tab>
</my-tabs>

Generate tabs dynamic

  • Using *ngFor directive to generate li element
<ul>
  <li *ngFor="let tab of tabs"></li>
</ul>
  • Create tabs property in TabsComponent, get children component with ContentChildren
    @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
    

    And how to test component with ViewChildren

  • Create test component in the spec file as the host
@Component({
  selector: 'test-my-tabs',
  template: ''
})
export class TestTabsComponent {
  constructor() { }
}
  • Create html template in spec file
  const html = `
    <my-tabs>
      <my-tab tabTitle="tab1">
        Tab 1 content
      </my-tab>
      <my-tab tabTitle="tab2">
        Tab 2 content
      </my-tab>
    </my-tabs>
  `;
  • Init Angular test component with the TestTabsComponent
  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [TestTabsComponent, TabsComponent, TabComponent]
    });
  }));

  beforeEach(() => {

    TestBed.overrideComponent(TestTabsComponent, { set: { template: html } });

    fixture = TestBed.createComponent(TestTabsComponent);

    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  • Override the component template with TestBed.overrideComponent before creating the test component and do not compile the component before override
  beforeEach(() => {

    TestBed.overrideComponent(TabsComponent, { set: { template: html } });

    fixture = TestBed.createComponent(TabsComponent);

    component = fixture.componentInstance;
    fixture.detectChanges();
  });
  • Then the children components will be created
  it('should display childrens components with the property tabs', () => {
    const tabs = fixture.debugElement.queryAll(By.css('li'));
    expect(tabs.length).toBe(2);
  });
  • And we can get TabsComponent instance with @ViewChild
@Component({
  selector: 'test-my-tabs',
  template: ''
})
export class TestTabsComponent {
  @ViewChild(TabsComponent) tabs: TabsComponent;
  constructor() { }
}

  it('should active the first tab as default', () => {
    const tabsComponent = component.tabs;
    expect(tabsComponent.tabs.first.active).toBeTruthy();
    expect(tabsComponent.tabs.last.active).toBeFalsy();
  });

Switch the tab content when clicking the tab title

Test first

  it('should siwtch tab when clicking the title', () => {
    const tabTitles = fixture.debugElement.queryAll(By.css('li'));
    const secondTabTitle = tabTitles[tabTitles.length - 1].nativeElement;

    secondTabTitle.click();

    expect(component.tabs.tabs.first.active).toBeFalsy('the first tab is unactive');
    expect(component.tabs.tabs.last.active).toBeTruthy('the tab clicked is active');
  });

Then bind click event to selectTab method

  <li *ngFor="let tab of tabs" (click)="selectTab(tab)">
    
  </li>
  selectTab(tab: TabComponent) {
    this.tabs.forEach(x => x.active = false);
    tab.active = true;
  }