Skip to content

Instantly share code, notes, and snippets.

@2J
Last active March 8, 2021 10:54
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save 2J/7e486786a056b24cfc816bff946b61a3 to your computer and use it in GitHub Desktop.
Save 2J/7e486786a056b24cfc816bff946b61a3 to your computer and use it in GitHub Desktop.
Angular 2 tabs example
<tabs>
<tab tabTitle="Tab 1">Tab 1 Content</tab>
<tab tabTitle="Tab 2" [active]="true">Tab 2 Content</tab>
<tab tabTitle="Tab 3" [disabled]="true">Tab 3 Content</tab>
<tab tabTitle="Link Tab 4" href="http://google.com">Link Tab</tab>
<tab tabTitle="Link Tab 5" href="http://google.com" [disabled]="true">Disabled Link Tab</tab>
</tabs>
/**
* This barrel file provides the export for the shared TabsComponent.
*/
export { TabsComponent } from './tabs.component';
export { TabComponent } from './tab.component';
export { OnTabSelect } from './on-tab-select.interface';
export { OnTabDeselect } from './on-tab-deselect.interface';
//Runs onTabDeselected() function when tab containing component is deselected
export abstract class OnTabDeselect {
abstract onTabDeselected(): void;
}
/**
* 1. import OnTabDeselect and implement in class:
* class ExampleComponent implements OnTabDeselect { onTabDeselected(){ } }
* 2. Add to component providers:
* providers: [{ provide: OnTabDeselect, useExisting: forwardRef(() => ExampleComponent) }]
*/
//Runs onTabSelected() function when tab containing component is selected
export abstract class OnTabSelect {
abstract onTabSelected(): void;
}
/**
* 1. import OnTabSelect and implement in class:
* class ExampleComponent implements OnTabSelect { onTabSelected(){ } }
* 2. Add to component providers:
* providers: [{ provide: OnTabSelect, useExisting: forwardRef(() => ExampleComponent) }]
*/
import { Component, Input, ContentChildren, QueryList, Output, EventEmitter } from '@angular/core';
import { OnTabSelect } from './on-tab-select.interface';
import { OnTabDeselect } from './on-tab-deselect.interface';
@Component({
selector: 'tab',
styles: [`
.pane{
padding: 1em;
}
`],
template: `
<div [hidden]="!active" class="pane">
<ng-content></ng-content>
</div>
`
})
export class TabComponent {
@Input() tabId: any;
@Input() tabTitle: string;
@Output()
selected: EventEmitter<null> = new EventEmitter<null>();
@Output()
deselected: EventEmitter<null> = new EventEmitter<null>();
private _active: boolean = false;
@Input()
get active(): boolean {
return this._active;
}
set active(active: boolean) {
let changed: boolean = this.active !== active;
this._active = active;
//Run onTabSelected for all tab children if active changed from false to true
if (changed && this.active === true) {
for (let onTabSelectComponent of this.onTabSelectComponents.toArray()) {
onTabSelectComponent.onTabSelected();
}
this.selected.emit();
}
if (changed && this.active === false) {
for (let onTabDeselectComponent of this.onTabDeselectComponents.toArray()) {
onTabDeselectComponent.onTabDeselected();
}
this.deselected.emit();
}
}
@Input() disabled: boolean = false;
@Input() href: string = ''; //TODO: Add option to make into link or routerlink
@ContentChildren(OnTabSelect)
onTabSelectComponents: QueryList<OnTabSelect>;
@ContentChildren(OnTabDeselect)
onTabDeselectComponents: QueryList<OnTabDeselect>;
}
import { Component, ContentChildren, QueryList, AfterContentInit, EventEmitter, Output } from '@angular/core';
import { TabComponent } from './tab.component';
import * as _ from 'lodash';
@Component({
selector: 'tabs',
template: `
<ul class="nav nav-tabs">
<li *ngFor="let tab of tabs" [class.active]="tab.active" [class.disabled]="tab.disabled">
<a role="tab" [href]="tab.href" (click)="selectTab(tab, $event)">
{{tab.tabTitle}}
</a>
</li>
</ul>
<ng-content></ng-content>
`
})
export class TabsComponent implements AfterContentInit {
@ContentChildren(TabComponent) tabs: QueryList<TabComponent>;
@Output() tabChangeEmitter = new EventEmitter<number>();
// contentChildren are set
ngAfterContentInit() {
// get all active tabs
let activeTabs = this.tabs.filter((tab) => tab.active);
// if there is no active tab set, activate the first
if (activeTabs.length === 0) {
this.selectTab(this.tabs.first);
}
}
findTabIndexById(id: any): number {
return _.findIndex(this.tabs.toArray(), { tabId: id });
/*//non-lodash implementation
let tabIndex = 0;
for (let tab of this.tabs.toArray()) {
if (tab.tabId === id) {
return tabIndex;
}
else {
++tabIndex;
}
}
return null;*/
}
findTabIndexByTitle(title: string): number {
return _.findIndex(this.tabs.toArray(), { tabTitle: title });
/*//non-lodash implementation
let tabIndex = 0;
for (let tab of this.tabs.toArray()) {
if (tab.tabTitle === title) {
return tabIndex
}
else {
++tabIndex;
}
}
return null;*/
}
selectTab(tab: TabComponent, event: Event = null, force: boolean = false): void {
let tabs = this.tabs.toArray();
if ((force || !tab.disabled) && !tab.href) {
let hasChanged = tab.active === false;
this.tabs.toArray().forEach(t => t.active = t === tab);
if (hasChanged) {
//Send event that active tab has changed
this.tabChangeEmitter.emit(this.currentIndex());
}
}
if (!!event && (!tab.href || (tab.href && (force || !tab.disabled)))) {
event.preventDefault();
}
}
selectTabByIndex(index: number): boolean {
let tabs = this.tabs.toArray();
if (index in tabs) {
//TODO: currently forcing tab change even if disabled because of current implementation
this.selectTab(tabs[index], null, true);
return true;
} else {
return false;
}
}
selectTabById(id: any): boolean {
let tabs = this.tabs.toArray();
let index = this.findTabIndexById(id);
if (index in tabs) {
//TODO: currently forcing tab change even if disabled because of current implementation
this.selectTab(tabs[index], null, true);
return true;
} else {
return false;
}
}
selectTabOffset(offset: number): boolean {
let currentIndex = this.currentIndex();
if (currentIndex === null) return false;
if ((currentIndex + offset) in this.tabs.toArray()) {
this.selectTabByIndex(currentIndex + offset);
return true;
} else {
return false;
}
}
selectTabNext(offset: number = 1): boolean {
return this.selectTabOffset(offset);
}
selectTabPrev(offset: number = 1): boolean {
return this.selectTabOffset(-offset);
}
currentIndex(): number {
for (let index in this.tabs.toArray()) {
if (this.tabs.toArray()[index].active) {
return Number(index);
}
}
return null; //no active tab found
}
}
@TeodorKolev
Copy link

TypeError: Cannot read property 'toArray' of undefined

@TeodorKolev
Copy link

How where must I import OnTabSelect and OnTabDeselect?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment