Reputation: 757
When using routerLink
and routerLinkActive
to apply CSS to a navigation bar, I'd like to also include the fragment
information so that links are unique for sections within a homepage.
I've tried using [routerLinkActiveOptions]="{ exact: true }"
wihtout any luck.
The relevant part of the navigation bar code is:
<li routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">
<a routerLink="/sitio" fragment="inicio">Inicio</a>
</li>
<li routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">
<a routerLink="/sitio" fragment="invierte">Invierte</a>
</li>
<li routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }">
<a routerLink="/sitio" fragment="contacto">Contacto</a>
</li>
The three different URLs the above code visits are:
/sitio#inicio
/sitio#invierte
/sitio#contacto
But when clickin any of them, all of them are marked as being active (because they correspond to the routerLink="/sitio"
and the fragment=*
information is not included in the check. This results in the navigation bar looking like this when clicking on any of them:
Any ideas on how to do this?
Upvotes: 18
Views: 8614
Reputation: 18268
Angular's routerLinkActiveOptions
provides fragment, queryParam, matrixParam, and path matching in order to match the active route without custom solutions.
If you look at RouteLinkActive you'll find it's a union:
routerLinkActiveOptions: { exact: boolean } | IsActiveMatchOptions;
Where IsActiveMatchOptions expands on the configuration to determine whether a router link is active by matching on any combination on fragments, queryParams, matrixParams, and paths:
export interface IsActiveMatchOptions {
matrixParams: 'exact'|'subset'|'ignored';
queryParams: 'exact'|'subset'|'ignored';
paths: 'exact'|'subset';
fragment: 'exact'|'ignored';
}
You pass them into routeLinkActiveOptions
instead of { exact: true }
:
<a [routerLinkActive]="linkActive"
[routerLink]="linkCommands"
[routerLinkActiveOptions]="linkActiveOptions"
[fragment]="fragment">...</a>
Created a quick stackblitz to demonstrate usage using a fragment: https://stackblitz.com/edit/angular-ivy-1vekqs?file=src/app/app.component.html
Upvotes: 8
Reputation: 810
The solution that Omar suggested worked, however, when I have a fragment that contains the name of another fragment in the name, a conflict is generated.
For example, a fragment called tab and another called table, when activating the table fragment, the tab will also be activated, as it has the word tab in its name.
I made some changes to this solution and I hope it helps other people.
component.ts
import { ActivatedRoute } from '@angular/router';
constructor(private route: ActivatedRoute){}
isSectionActive(section: string): boolean {
let element = false;
this.route.fragment.subscribe((fragment: string) => {
element = fragment === section.split("#").pop();
});
return element;
}
component.html
<button type="button" class="item" [routerLink]="['/components/tutorial']" [fragment]="fragmentName" [class.active]="isSectionActive(fragmentName)">{{fragmentName}}</button>
Upvotes: 0
Reputation: 1891
I struggle to work with [routerLinkActiveOptions]="{ exact: true }"
even on the Angular 7.
So, I used the following trick to solve this issue. The answer is ActivatedRoute
.
in the Component
.
class ComponentClass{
activeFragment = this.route.fragment.pipe(share());
constructor(public route: ActivatedRoute){}
}
In the template
use this as follows.
<li [class.active]="(activeFragment | async)==='inicio'">
<a routerLink="/sitio" fragment="inicio">Inicio</a>
</li>
<li [class.active]="(activeFragment | async)==='invierte'">
<a routerLink="/sitio" fragment="invierte">Invierte</a>
</li>
<li [class.active]="(activeFragment | async)==='contacto'">
<a routerLink="/sitio" fragment="contacto">Contacto</a>
</li
I am not sure about the share
operator here. I haven't looked into it if its multicast by default.
Let me know if you have an answer to that.
Upvotes: 1
Reputation: 515
Making Omar Trejo's approach simpler. Rather than subscribing to the Route, I updated the isSectionActive to look at location.href to make it work.
private isSectionActive(section: string): boolean {
return location.href.indexOf(section) !== -1;
}
Upvotes: 0
Reputation: 757
I just thought I would put how I solved it in case someone runs into the same problem: keep track of the current section in the site and use a class binding through a function in the component instead of using routerLinkActive
.
The links in the template become:
<li [class.active]="isSectionActive('inicio')">
<a routerLink="/sitio" fragment="inicio">Inicio</a>
</li>
<li [class.active]="isSectionActive('invierte')">
<a routerLink="/sitio" fragment="invierte">Invierte</a>
</li>
<li [class.active]="isSectionActive('contacto')">
<a routerLink="/sitio" fragment="contacto">Contacto</a>
</li>
Note the lack of use of routerLinkActive
, and the use of class binding [class.active]=isSectionActive('<name-of-section>')
instead.
The code that keeps track of the section we're inside of and decides whether or not to apply the CSS class in the template is:
import { Router, NavigationEnd } from '@angular/router';
// Class signature... {
private activeSiteSection: string;
constructor(
private router: Router,
private sessionService: SessionService
) {
router.events.subscribe((event) => {
if(event instanceof NavigationEnd ) {
this.SiteURLActiveCheck(event);
}
});
}
private SiteURLActiveCheck(event: NavigationEnd): void {
if (event.url.indexOf('#inicio') !== -1) {
this.activeSiteSection = 'inicio';
} else if (event.url.indexOf('#invierte') !== -1) {
this.activeSiteSection = 'invierte';
} else if (event.url.indexOf('#contacto') !== -1) {
this.activeSiteSection = 'contacto';
} else {
this.activeSiteSection = '';
}
}
private isSectionActive(section: string): boolean {
return section === this.activeSiteSection;
}
}
Probably overkill but I rather go this route than modify Angular 2's source. :)
Upvotes: 12
Reputation: 657318
There is an open issue to support that https://github.com/angular/angular/issues/13205
routerLinkActive
is a simple directive. You might be able to create a clone yourself with that extended functionality.
Upvotes: 4