trungvose
trungvose

Reputation: 20034

Angular - routerLinkActive and queryParams handling

Update March 2021

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
}

Original Question

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

Answers (4)

BondAddict
BondAddict

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

botmachine
botmachine

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

DougS
DougS

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

s-f
s-f

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

Related Questions