Reputation: 20034
Angular team finally merge the PR for that, you can looks at https://github.com/angular/angular/pull/40303 for more detail.
To use it, simply put
<div routerLinkActive="active-link" [routerLinkActiveOptions]="options">
</div>
Where options
will have the either the shape of IsActiveMatchOptions
export declare interface IsActiveMatchOptions {
fragment: 'exact' | 'ignored';
matrixParams: 'exact' | 'subset' | 'ignored';
paths: 'exact' | 'subset';
queryParams: 'exact' | 'subset' | 'ignored';
}
or simply accept a boolean exact
{
exact: boolean
}
I am using routerLink in Angular as below
<li routerLinkActive="active">
<a [routerLink]="['/view', 10]"
[queryParams]="{'offset': 0, 'numberOfItems': 100}"
queryParamsHandling="merge">
<div>View</div>
<div><span class="badge" [innerHtml]="viewCount"></span></div>
</a>
</li>
So the .active class was added to li tag when the URL looks like
/view/10?offset=0&numberOfItems=100
If the URL was changed either offset
or numberOfItems
to a different value, the .active class will be removed, such as.
/view/10?offset=0&numberOfItems=120
I went through the angular docs and managed to make it works by adding another routerLink but make it hidden as below.
<li routerLinkActive="active">
<a [routerLink]="['/view', 10]"
[queryParams]="{'offset': 0, 'numberOfItems': 100}"
queryParamsHandling="merge">
<div>View</div>
<div><span class="badge" [innerHtml]="viewCount"></span></div>
</a>
<a [routerLink]="['/view', 10]" style="display:none;">
<div><span class="badge" [innerHtml]="viewCount"></span></div>
</a>
</li>
Seem the above approach is a little hack. Is there any configuration that I can set to make routerLinkActive works when router parameter presents with optional query parameter?
Upvotes: 11
Views: 11353
Reputation: 800
I tweaked the answers above for Angular 14.
import {
ChangeDetectorRef,
Directive,
ElementRef,
Input,
Optional,
Renderer2,
SimpleChanges,
} from '@angular/core';
import {
Router,
RouterLink,
RouterLinkActive,
RouterLinkWithHref,
UrlTree,
} from '@angular/router';
@Directive({
selector: '[myRouterLinkActive]',
})
export class MyRouterLinkActiveDirective extends RouterLinkActive {
@Input()
set myRouterLinkActive(data: string[] | string) {
const classes = Array.isArray(data) ? data : data.split(' ');
(<any>this).classes = classes.filter((c) => !!c);
}
constructor(
router: Router,
element: ElementRef,
renderer: Renderer2,
cdr: ChangeDetectorRef,
@Optional() link?: RouterLink,
@Optional() linkWithHref?: RouterLinkWithHref
) {
super(router, element, renderer, cdr, link, linkWithHref);
(<any>MyRouterLinkActiveDirective).prototype.isLinkActive =
this.IsLinkActive;
}
/**
* this method sets the link as active regardless of query params
* @param router
* @private
*/
private IsLinkActive(
router: Router
): (link: RouterLink | RouterLinkWithHref) => boolean {
return (link: RouterLink | RouterLinkWithHref) => {
const urlTree = link.urlTree as UrlTree;
const linkPath =
'/' + urlTree.root.children['primary'].segments.join('/');
return router.url.indexOf(linkPath) != -1;
};
}
override ngOnChanges(changes: SimpleChanges) {
super.ngOnChanges(changes);
}
override ngOnDestroy() {
super.ngOnDestroy();
}
override ngAfterContentInit() {
super.ngAfterContentInit();
}
}
The tweaks were adding the override keyword to the callback methods, casting the urlTree as a type of UrlTree, removing the isActive() since its deprecated and just checking for if the string has an index of the route value I'm trying to match.
Huge shoutout to the guys above though!
Upvotes: 1
Reputation: 11
The solution works with Angular 11. Sets the link as active regardless of different query params (only segments/path is compared).
import * as _ from 'lodash';
import {
AfterContentInit,
ChangeDetectorRef,
Directive,
ElementRef, Input,
OnChanges,
OnDestroy,
Optional,
Renderer2, SimpleChanges
} from '@angular/core';
import { Router, RouterLink, RouterLinkActive, RouterLinkWithHref } from '@angular/router';
@Directive({
selector: '[rcRouterLinkActive]',
exportAs: 'rcRouterLinkActive',
})
export class RcRouterLinkActiveDirective extends RouterLinkActive implements OnChanges, OnDestroy, AfterContentInit {
@Input()
set rcRouterLinkActive(data: string[]|string) {
const classes = Array.isArray(data) ? data : data.split(' ');
(<any>this).classes = classes.filter(c => !!c);
}
constructor(
router: Router,
element: ElementRef,
renderer: Renderer2,
cdr: ChangeDetectorRef,
@Optional() link?: RouterLink,
@Optional() linkWithHref?: RouterLinkWithHref
) {
super(router, element, renderer, cdr, link, linkWithHref);
(<any>RcRouterLinkActiveDirective).prototype.isLinkActive = this.isRcLinkActive;
}
/**
* this method sets the link as active regardless of query params
* @param router
* @private
*/
private isRcLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
return (link: RouterLink|RouterLinkWithHref) =>{
const linkPath = '/' + link.urlTree.root.children.primary.segments.join('/');
return router.isActive(link.urlTree, this.routerLinkActiveOptions.exact) ||
_.startsWith(router.url, linkPath);
}
}
ngOnChanges(changes: SimpleChanges) {
super.ngOnChanges(changes);
}
ngOnDestroy() {
super.ngOnDestroy();
}
ngAfterContentInit() {
super.ngAfterContentInit();
}
}
Usage (works as standard angular's routerLinkActive):
<a ... rcRouterLinkActive="active" ...>Link text</a>
Upvotes: 1
Reputation: 125
You may find this helpful - extend RouterLinkActive - an item is 'selected' if it contains a string...
import { Directive, Input, OnChanges, ElementRef, Renderer2, ChangeDetectorRef, SimpleChanges, Injector, AfterContentInit } from '@angular/core';
import { Router,RouterLinkActive, RouterLink, RouterLinkWithHref } from '@angular/router';
@Directive({
selector: '[routerLinkActiveQS]',
exportAs: 'routerLinkActiveQS'
})
export class RouterLinkQsactiveDirective extends RouterLinkActive implements OnChanges, AfterContentInit
{
@Input()
set routerLinkActiveQS(data: string[]|string) {
(<any>this).routerLinkActive = data;
}
@Input() routerLinkActiveQSContains : string;
constructor ( injector: Injector ) {
super ( injector.get(Router),injector.get(ElementRef),injector.get(Renderer2),injector.get(ChangeDetectorRef));
(<any>RouterLinkQsactiveDirective.prototype).isLinkActive = this.MyisLinkActive;
}
ngOnChanges(changes: SimpleChanges): void {
super.ngOnChanges(changes);
}
ngAfterContentInit(): void {
super.ngAfterContentInit();
}
private MyisLinkActive(router: Router): (link: (RouterLink|RouterLinkWithHref)) => boolean {
const result = (link: RouterLink | RouterLinkWithHref) => {
let resultInsde = router.isActive(link.urlTree, this.routerLinkActiveOptions.exact);
resultInsde = resultInsde || router.url.includes ( this.routerLinkActiveQSContains );
return resultInsde;
}
return result;
}
}
Upvotes: 1
Reputation: 2143
I run into the same issue, but it looks this's by design according this https://github.com/angular/angular/issues/13205
vsavkin is a router creator.
Upvotes: 1