Angular 6 Material: mat-tab-link be selected by underlining bar
Asked Answered
C

6

22

I have a mat-tab-nav-bar navigation bar for my website, but the mat-tab-link blue underlining bar won't chase the active button. It just stays at the first button, and doesn't move. The buttons do turn into active state though in the sense that the background color changes, and they route well to their corresponding pages.

Here's the app.component.ts:

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

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

export class AppComponent {
  navLinks = [
    { path: '', label: 'The Problem' },
    { path: 'the-solution', label: 'The Solution' },
    { path: 'the-game', label: 'The Game' },
    { path: 'probability-calculator', label: 'Probability calculator' },
  ];
}

And here's the app.component.html:

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="link.path"
     routerLinkActive #rla="routerLinkActive"
     [active]="rla.isActive">
    {{link.label}}
  </a>
</nav>

<router-outlet></router-outlet>

Here is the app.module.ts:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { MatTabsModule } from '@angular/material/tabs';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { AppRoutingModule } from './app-routing.module';

import { AppComponent } from './app.component';
import { TheProblemComponent } from './the-problem/the-problem.component';
import { TheSolutionComponent } from './the-solution/the-solution.component';
import { ProbabilityCalculatorComponent } from './probability-calculator/probability-calculator.component';
import { TheGameComponent } from './the-game/the-game.component';

@NgModule({
  declarations: [
    AppComponent,
    TheProblemComponent,
    TheSolutionComponent,
    ProbabilityCalculatorComponent,
    TheGameComponent
  ],
  imports: [
    AppRoutingModule,
    BrowserModule,
    BrowserAnimationsModule,
    MatTabsModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

What am I missing? Thank you!

EDIT

I edited the app.component.html like this to find out some more about the link "active" state:

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="link.path"
     routerLinkActive #rla="routerLinkActive"
     [active]="rla.isActive">
    {{link.label}}
    <div style="color: red; margin-left: 10px;">
        <span *ngIf="rla.isActive"> Is active!</span>
        <span *ngIf="!rla.isActive"> Is not active...</span>
    </div>
  </a>
</nav>

<router-outlet></router-outlet>

As it turns out, the first link in the menu always remains active (rla.isActive) - also when I'm navigating to other pages. All other links turn off their active state just fine, and only get activated when they are navigated to. How do I turn off the active state of the first link when navigating to other links?

EDIT 2

Adding app-routing.module.ts code:

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

import { TheProblemComponent } from './the-problem/the-problem.component';
import { TheSolutionComponent } from './the-solution/the-solution.component';
import { TheGameComponent } from './the-game/the-game.component';
import { ProbabilityCalculatorComponent } from './probability-calculator/probability-calculator.component';

const routes: Routes = [
    { path: '', component: TheProblemComponent },
    { path: 'the-solution', component: TheSolutionComponent },
    { path: 'the-game', component: TheGameComponent },
    { path: 'probability-calculator', component: ProbabilityCalculatorComponent }
];


@NgModule({
  imports: [ RouterModule.forRoot(routes) ],
  exports: [ RouterModule ]
})
export class AppRoutingModule { }
Cavender answered 13/5, 2018 at 6:7 Comment(5)
The empty route path will always resolve for all paths, therefore routerLinkActive will always be true.Thickset
Yes!! That's it, thank you! How can I give a router path to root though, without having it resolve for all paths? The first "Home"-button is usually to root! I tried using /, but I get a console error Error: Invalid configuration of route '/': path cannot start with a slash.Cavender
Maybe you can try a home route and by default, your app will redirect to the home route?Thickset
I'd really prefer being able to have a root URL, since it looks nice to have a clean root URL for the home page. Is this not possible? This is an advanced framework, should be possible IMO! Thanks!Cavender
@MarcusEdensky i am facing exactly the same issue. did u find any solution to use the empty root path?Ziska
P
32

It doesn't work for you because every has the same #rla variable

You can do it this way:

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="link.path"
     routerLinkActive #rla="routerLinkActive"
     [active]="link.isActive">
    {{link.label}}
  </a>
</nav>

<router-outlet></router-outlet>

or with {exact:true}

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="link.path"
     routerLinkActive #rla="routerLinkActive"
     [routerLinkActiveOptions]="{exact:true}"
     [active]="rla.isActive">
    {{link.label}}
  </a>
</nav>

<router-outlet></router-outlet>
Portaltoportal answered 4/7, 2018 at 13:36 Comment(1)
Your answer was super helpful for me to understand the problem because I was not using *ngFor to render the links. In such case, naming the local # variable differently for every link did do the job.Cornett
S
14

Using different local variables(#rla1, #rla2) worked for me, as i was not using *ngFor:

<nav mat-tab-nav-bar>
    <a mat-tab-link
       [routerLink]="['/home/homepage/dashboard/']"
       routerLinkActive #rla1="routerLinkActive"
       [active]="rla1.isActive">Dashboard
    </a>
    <a mat-tab-link
       [routerLink]="['/home/homepage/reports/']"
       routerLinkActive #rla2="routerLinkActive"
       [active]="rla2.isActive">Reports
    </a>
</nav>
Siding answered 20/6, 2019 at 5:37 Comment(0)
T
9

You're missing a / before the link:

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="['/'+link.path]"
     routerLinkActive #rla="routerLinkActive"
     [active]="rla.isActive">
    {{link.label}}
  </a>
</nav>

<router-outlet></router-outlet>

EDIT: The right way here is to set a non-empty path for all routes. You can then use a wildcard with a redirect.

const APP_ROUTES: Route[] = [
  { path: 'path-1', component: OneComponent },
  { path: 'path-2', component: TwoComponent },
  { path: 'path-3', component: ThreeComponent },
  { path: '**', redirectTo: 'path-1' },
]
Thickset answered 13/5, 2018 at 14:3 Comment(5)
Thank you for your response Edric, but unfortunately didn't do any difference. It's strange, because I'm using the documentation material.angular.io/components/tabs/api#MatTabLink , and I'm new to Angular 6, so I'm not really sure what I'm missing.Cavender
@MarcusEdensky Did you correctly import RouterModule? Can you add more code showing your app's module?Thickset
I believe I did, but please have a look! I just added the app.module.ts code. Thanks!Cavender
not working for me, the underlining bar won't chase the expected item consistently. Also, this breaks the animation of the bar.Sidonnie
@MarcusEdensky i am facing exactly the same issue. did u find any solution to use the empty root path?Ziska
B
6

You need to add [routerLinkActiveOptions]="{exact:true}" to the a tag. It becomes

<nav mat-tab-nav-bar>
  <a mat-tab-link
     *ngFor="let link of navLinks"
     [routerLink]="link.path"
     routerLinkActive #rla="routerLinkActive"
     [routerLinkActiveOptions]="{exact:true}"
     [active]="rla.isActive">
    {{link.label}}
    <div style="color: red; margin-left: 10px;">
        <span *ngIf="rla.isActive"> Is active!</span>
        <span *ngIf="!rla.isActive"> Is not active...</span>
    </div>
  </a>
</nav>

<router-outlet></router-outlet>
Bathsheba answered 2/7, 2018 at 21:38 Comment(1)
I think it should still be allowed to work with the exact:false default but it does notBeethoven
U
0

You can simply follow below code.active is simply bootstrap class.

<nav mat-tab-nav-bar>
  <a mat-tab-link
  *ngFor="let link of navLinks"
  [routerLink]="['/'+link.path]"
  routerLinkActive="active">
  {{link.label}}
  </a>
</nav>

OR you can do like this below instead of using material design. kindly notice routerLinkActiveOptions is used.

<ul class="nav nav-tabs">
    <li role="presentation"
        routerLinkActive="active"
        [routerLinkActiveOptions]="{exact: true}">
      <a routerLink="/">The Problem</a>
    </li>
    <li role="presentation"
        routerLinkActive="active">
      <a routerLink="/the-solution">The Solution</a>
    </li>
    <li role="presentation"
        routerLinkActive="active">
      <a [routerLink]="/the-game">The Game</a>
    </li>
    ....
    ....
 </ul>  
Upheave answered 18/5, 2018 at 9:40 Comment(1)
Bootstrap class?Craniology
B
0

I came up with a Directive to achieve the same effect as the other answers without cluttering templates:

@Directive({selector: 'a[routerLinkActive][mat-tab-link]'})
class MatTabRouterLinkActiveDirective {
    constructor(routerLinkActive: RouterLinkActive, matTabLink: MatTabLink) {
        routerLinkActive.isActiveChange.subscribe(value => matTabLink.active = value);
    }
}

With this directive included in your module, all you need is the routerLinkActive attribute and the directive will take care of the rest:

<a mat-tab-link routerLink="/foo" routerLinkActive>Foo</a>

This gets rid of the #rlaXXX="routerLinkActive" [active]="rlaXXX.isActive" boilerplate which can get quite repetitive when the tabs are listed directly in the template (as opposed to being generated from *ngFor or similar).

Beitnes answered 28/5, 2022 at 18:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.