Creating tabs using Angular Material 5 and Angular 5 Routing

This is a follow up article of Creating tabs using Angular Material 2 and Angular 4 Routing.

In this article, I will show how to create tabs using Angular Material 5.2.4 and Angular 5.2.8 Routing. I will be using mat-tab-nav-bar and mat-tab-link to create my tabs. Below is the functionality I would like to achieve from the tabs I create:

  • The number of tabs I want to display on the UI is directly dependent on number of products owned by the user. Each tab will have it’s own product description and fields which is unique to the product.
  • I want the tab headers to be rectangular boxes. The default Angular Material theme does not display tab headers as boxes. I will modify the default theme and create my own styling.
  • The default selected tab is the first tab which is displayed by an animated underline. I want the default tab to be other than the first tab.
  • I wanted to show the selected tab in different colour.
  • When I refreshed the page, it should maintain the tab selection depending on which route I am on. The default behaviour of Material 2 tabs takes the animated underline back to the default tab instead of keeping it under the selected tab.

Here is how the final outcome will look like:

 

PLEASE NOTE: You must have knowledge and understanding of Node, TypeScript and Angular 5 or higher.

You will require to add the below three packages as part of your "dependencies" in your package.json

  • @angular/animations
  • @angular/cdk
  • @angular/material

Apart from the above three packages, you will also need to include Angular Material theme in your application. You can learn more about material themes here. Below are the versions of angular packages I used for putting my code together. These are the latest version as of today.

  • Angular CLI version 1.7.3
  • Angular version 5.2.8
  • Angular Material version 5.2.4

 

The complete code is available on my GitHub that you can download or fork.

 

My application structure layout:

src
|--css
|   |--ndevre-material-theme.css
|
|--index.html
|--styles.css
|
|--app
    |-- app.component.css
    |-- app.component.html
    |-- app.component.ts
    |-- app.routing.module.ts
    |-- app.module.ts
    |-- products
            |-- product 1
                    |-- product-1.component.html
                    |-- product-1.component.css
                    |-- product-1.component.ts
            |-- product 2
                    |-- product-2.component.html
                    |-- product-2.component.css
                    |-- product-2.component.ts
            |-- product 3
                    |-- product-3.component.html
                    |-- product-3.component.css
                    |-- product-3.component.ts
            |-- product 4
                    |-- product-4.component.html
                    |-- product-4.component.css
                    |-- product-4.component.ts
            |-- product 5
                    |-- product-5.component.html
                    |-- product-5.component.css
                    |-- product-5.component.ts

app.component.ts

In this app.component.ts file, I have initialized my component with a routeLinks array in the constructor, which holds all the information needed to create the tabs. I could have used mocking to create the tabs dynamically but I wanted to keep it simple.

The this.router.events.subscribe code block in ngOnInit() function checks which route is selected i.e. which tab was last clicked and selects the same tab when page is refreshed. This solves my fourth requirement mentioned above, i.e. “When I refreshed the page, it should maintain the tab selection depending on which route I am on..”

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

@Component({
    selector: 'app-root',
    templateUrl: './app.component.html',
    styleUrls: ['./app.component.css']
})

export class AppComponent implements OnInit {
    routeLinks: any[];
    activeLinkIndex = -1;

    constructor(private router: Router) {
        this.routeLinks = [
            {
                label: 'Product 1',
                link: './product1',
                index: 0
            }, {
                label: 'Product 2',
                link: './product2',
                index: 1
            }, {
                label: 'Product 3',
                link: './product3',
                index: 2
            }, {
                label: 'Product 4',
                link: './product4',
                index: 3
            }, {
                label: 'Product 5',
                link: './product5',
                index: 4
            }
        ];
    }

    ngOnInit(): void {
        this.router.events.subscribe((res) => {
            this.activeLinkIndex = this.routeLinks.indexOf(this.routeLinks.find(tab => tab.link === '.' + this.router.url));
        });
    }

    getActiveClass(indexOfRouteLink) {
      let tabsclass = 'mat-tab-link';
      if (this.activeLinkIndex === indexOfRouteLink) {
        tabsclass = 'mat-tab-link mat-tab-label-active';
      }

      return tabsclass;
    }
}

app.component.html

In app.component.html, we will create the tabs. The *ngFor="let routeLink of routeLinks; let i = index;" loops over the routeLinks array we created in app.component.ts file above.

The [routerLink]="routeLink.link" links the tab headers to it’s own routes. These routes are defined in app.routing.module.ts below.

<div style="padding: 5px;">
    <h1>Angular Material 5 Tab Example using mat-tab-nav-bar and mat-tab-link.</h1>
    <nav mat-tab-nav-bar >
        <a mat-tab-link
           *ngFor="let routeLink of routeLinks; let i = index;"
           [routerLink]="routeLink.link"
           routerLinkActive
           #rla="routerLinkActive"
           [active]="activeLinkIndex === i"
           (click)="activeLinkIndex = i"
           [routerLinkActiveOptions]="{exact: true}"
           [class] = getActiveClass(i)
        >
            {{routeLink.label}}
        </a>
    </nav>

    <router-outlet></router-outlet>
</div>

app.routing.module.ts

In this app.routing.module.ts, we will define all the routes for the products that we want the tabs to navigate to.

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

import { Product1Component } from './products/product-1/product-1.component';
import { Product2Component } from './products/product-2/product-2.component';
import { Product3Component } from './products/product-3/product-3.component';
import { Product4Component } from './products/product-4/product-4.component';
import { Product5Component } from './products/product-5/product-5.component';

const routes: Routes = [
    {
        path: '',
        redirectTo: '/product1',
        pathMatch: 'full'
    },
    { path: 'product1', component: Product1Component },
    { path: 'product2', component: Product2Component },
    { path: 'product3', component: Product3Component },
    { path: 'product4', component: Product4Component },
    { path: 'product5', component: Product5Component },
    { path: '**', redirectTo: '/product1', pathMatch: 'full' }
];

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

product-1, product-2, product-3, product-4, product-5

These product-1, product-2, product-3, product-4, product-5 folders under the products folder will have components that holds form fields and information related to the products.

app.module.ts

In this app.module.ts file, we will include app.routing.module we created above as well as the MatTabsModule from @angular/material module.

import { BrowserModule } from '@angular/platform-browser';
import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';
import {AppRoutingModule} from './app.routing.module';
import { MatTabsModule } from '@angular/material';
import { HttpClientModule } from '@angular/common/http';

import { Product1Component } from './products/product-1/product-1.component';
import { Product2Component } from './products/product-2/product-2.component';
import { Product3Component } from './products/product-3/product-3.component';
import { Product4Component } from './products/product-4/product-4.component';
import { Product5Component } from './products/product-5/product-5.component';

import {AppComponent} from './app.component';

@NgModule({
    declarations: [
        AppComponent,
        Product1Component,
        Product2Component,
        Product3Component,
        Product4Component,
        Product5Component,
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        MatTabsModule,
        HttpClientModule
    ],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule { }


styles.css

This is where I styled my tabs that I mentioned in the beginning of this article. i.e. “I wanted the tab headers to be rectangular boxes” and “I wanted to show the selected tab in different colour

[_nghost-c5] {
    display: inline-block !important;
    width: 100%;
}
.mat-tab-link-active,
.mat-tab-label-active {
    background-color: #5EADB0;
    color: #D5FEFF;
    border: 1px solid #6B7F7F;
    font-weight: bold;
}

.mat-tab-link,
.mat-tab-label {
    line-height: 30px !important;
    height: 30px !important;
    min-width: 100px !important;
    border: 1px solid #7e7e7e;
}

.tabContentContainer {
    border: 1px solid #aaaaaa;
    background: #ffffff 50% 50% repeat-x;
}

The complete code is available on my GitHub that you can download or fork.