3

I have developed a custom tab component using Angular 2 which is working fine. This is the usage of it.

<us-tab-verticle>
    <vtab-content [tabTitle]="'Basic Information'"><basic-info> </basic-info></vtab-content>
    <vtab-content [tabTitle]="'VAT Settings'"><vat-settings> </vat-settings></vtab-content>
    <vtab-content [tabTitle]= "'Economy Settings'"><economy-settings> </economy-settings></vtab-content>
    <vtab-content [tabTitle]="'Access Profiles'"><access-profiles> </access-profiles></vtab-content>
</us-tab-verticle>

The problem is all the tab components are loading when the view load.

My tab implementation is as follows.

us-tab-verticle.component.html

<div class="v-tabs">
  <div class="v-tabs-col v-tabs-left">
    <ul class="nav nav-v-tabs flex-column" role="tablist">
      <li class="nav-v-item" *ngFor="let tab of tabs" (click)="selectTab(tab)">
        <a [class.active]="tab.active" class="nav-v-link" data-toggle="tab" href="#" role="tab">{{tab.title}}</a>
      </li>
    </ul>
  </div>

  <div class="v-tabs-col v-tabs-fill">
    <div class="v-tabs-content">
      <div>
      <ng-content></ng-content>
      </div>
    </div>
  </div>

us-tab-verticle.component.ts

import { Component, ContentChildren, QueryList, AfterContentInit } from '@angular/core';
import { VtabContentComponent } from './vtab-content/vtab-content.component';

@Component({
  selector: 'us-tab-verticle',
  templateUrl: './us-tab-verticle.component.html',
  styleUrls: ['./us-tab-verticle.component.scss']
})
export class UsTabVerticleComponent implements AfterContentInit {

  @ContentChildren(VtabContentComponent) tabs: QueryList<VtabContentComponent>;

  // contentChildren are set
  ngAfterContentInit() {
    // get all active tabs
    const 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);
    }
  }

  selectTab(tab: VtabContentComponent) {
    // deactivate all tabs
    this.tabs.toArray().forEach(tab => tab.active = false);

    // activate the tab the user has clicked on.
    tab.active = true;
  }

}

vtab-content.component.html

<div class="tab-content v-tab-content align-content-stretch">
  <div [hidden]="!active" class="tab-pane active" role="tabpanel">
    <ng-content></ng-content>
  </div>
</div>

vtab-content.component.ts

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

@Component({
  selector: 'vtab-content',
  templateUrl: './vtab-content.component.html',
  styleUrls: ['./vtab-content.component.scss']
})

export class VtabContentComponent  {
  @Input('tabTitle') title: string;
  @Input() active = false;
}

I need to load each component when I click the header of the each tabs. I sow that NgComponentOutlet can use to this kind of situations. But could not get any idea how to implement.

3
  • make use of dynamic components or lazy loading of compoennts Commented Aug 30, 2017 at 7:27
  • For this issue, we have many ways to solve it, I suggest you look for the material2 tabs that has a solution implemented. Commented Aug 31, 2017 at 14:02
  • you can use *ngIf="active" instead of [hidden] that way it will only insert the component when true Commented Aug 31, 2017 at 15:01

2 Answers 2

3

There are many solutions, if you want to improve your solution i suggest the usage of *ngIf. But notice that you destroy and create a new Component every time the *ngIf state changes.

I suggest you to take a look at RouterModule with the use of children attribue.

A little sample : (not sure you can make it work right away but I use similar stuff in my app)

// Router.ts
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';

const routes: Routes = [
    {
        path: '',
        children: [
            { path: 'tab1', component: Tab1Component },
            { path: 'tab2', component: Tab2Component },
        ]
    },
    { path: '**', redirectTo: '' } // modify for default tab?
];


@NgModule({
    imports: [RouterModule.forRoot(routes)],
    exports: [RouterModule]
})
export class AppRoutingModule { }

export const routedComponents = [Tab1Component, Tab2Component];

// AppModule.ts
imports: [AppRoutingModule]
declarations: [
    routedComponents
],

// main.html
<app-header>
<us-tab-verticle>
<div class="tab" [routerLink]="['/tab1']"><a>
<div class="tab" [routerLink]="['/tab2']"><a>
<router-outlet></router-outlet> // the content of your tab components will be displayed below
<app-footer>

This way in my opinion make your app easier to read (declarative routing) instead of having an external service and component for managing the states of your tabs.

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

1 Comment

*ngIf does not work with my implementation. I will try with router change option.
1
+50

I try to reproduce your structure with the usage of the ngComponentOutlet directive. Here is the tab-container:

@Component({
  selector: 'tab',
  template: ''
})
export class TabComponent {
  @Input() title: string;
  @Input() contentRef: any;
  active = false;
}

This is a very simple component which knows its own tab name, an active state and the body component reference which should be loaded when somebody selects the tab.

Then we create several body components which will be loaded dynamically:

@Component({
  selector: 'tab-content',
  template: `<p>Hey</p>`
})
export class TabContentComponent {}

@Component({
  selector: 'tab-content-alternative',
  template: `<p>Hey, this is an alternative content</p>`
})
export class TabContentAlternativeComponent {}

Here is the tabs-container component with tabs rendering and an empty placeholder for dynamic body components:

@Component({
  selector: 'tab-container',
  template: `
    <div class="tab-header">
      <div class="tab" *ngFor="let tab of tabs" (click)="selectTab(tab)" [class.active]="tab.active">{{tab.title}}</div>
    </div>

    <div class="tab-content">
      <ng-container *ngComponentOutlet="content | async"></ng-container>
    </div>
  `,
})
export class TabContainerComponent implements AfterContentInit {
  @ContentChildren(TabComponent) tabs: QueryList<TabComponent>;

  private contentSbj = new BehaviorSubject<BasicContent>(null);
  content = this.contentSbj.asObservable();

  ngAfterContentInit() {
    const activeTabs = this.tabs.filter((tab) => tab.active);
    if (activeTabs.length === 0) {
      this.selectTab(this.tabs.first);
    }
  }

  selectTab(tab: TabComponent) {
    this.tabs.toArray().forEach(tab => tab.active = false);
    tab.active = true;
    this.contentSbj.next(tab.contentRef);
  }
}

And this is how it can be used in some parent component:

import {TabContentComponent} from './tab/tab-content.component'
import {TabContentAlternativeComponent} from './tab/tab-content-alternative.component'
...

@Component({
  selector: 'my-app',
  template: `
    <tab-container>
      <tab title="Tab 1" [contentRef]="normalContent"></tab>
      <tab title="Tab 2" [contentRef]="alternativeContent"></tab>
    </tab-container>
  `,
})
export class App {
  normalContent = TabContentComponent;
  alternativeContent = TabContentAlternativeComponent;
}

Here is working Plunkr

3 Comments

According to your solution title-content-mapping is a dependency of the TabContainerComponent. So this can not use as a common component. My current implementation can use any where of the project and can create any number of tabs.
The idea was to have some mapping between tab names and their content. I can get rid of it and pass a content component into a tab as an input parameter directly. Updated the plunkr slightly.
Please check out my final solution in this plunkr. Now tab-container component can have as many tabs as needed (see app.ts) without any external dependencies and all the logic is hidden inside. Updated the answer too.

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.