skip to content

In Angular, why does routerLinkActive keep highlighting a Home link to '/' on every page, and how do routerLinkActiveOptions fix it?

level: middleimportance: should knowfreq 48%

answer

  1. subset match by default
  2. the root prefixes everything
  3. exact also compares the query
  4. paths, queryParams, matrixParams, fragment

basics

~20 s

RouterLinkActive matches by subset by default, so a link to '/' is contained in every URL and stays active. Setting routerLinkActiveOptions to {exact: true}, or to IsActiveMatchOptions with paths 'exact', restricts it to the exact URL.

solid answer

~40 s

`routerLinkActive` adds its classes when the link's `UrlTree` is contained in the current URL. The default is a **subset** match: `paths: 'subset'`, `queryParams: 'subset'`, matrix params and fragment ignored. The root `/` is a subset of every URL, so Home is always active. `[routerLinkActiveOptions]="{exact: true}"` switches to `paths: 'exact'` and `queryParams: 'exact'`, which fixes Home but also makes a `/products` tab go inactive on `/products?page=2`. For that case pass the object form, `{paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored'}`. The directive can sit on a parent such as an `<li>` and watches descendant `routerLink`s, updates after `NavigationEnd`, exposes `isActiveChange` and `#rla="routerLinkActive"`, and sets `aria-current` when you pass `ariaCurrentWhenActive`.

code

ts · 19 lines
ts
import {Component} from '@angular/core';
import {RouterLink, RouterLinkActive} from '@angular/router';

@Component({
  selector: 'app-main-nav',
  imports: [RouterLink, RouterLinkActive],
  template: `
    <ul>
      <li routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" ariaCurrentWhenActive="page">
        <a routerLink="/">Home</a>
      </li>
      <li routerLinkActive="active" ariaCurrentWhenActive="page"
          [routerLinkActiveOptions]="{paths: 'subset', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored'}">
        <a routerLink="/products">Products</a>
      </li>
    </ul>
  `,
})
export class MainNav {}

go deeper

for a junior

Know that routerLinkActive adds classes while a link matches and that {exact: true} stops a root link from staying active everywhere.

for a middle

Explain the subset default across paths and query params, why exact breaks tabs on paged URLs, and how the IsActiveMatchOptions object sets each axis.

for a senior

Build navigation that is correct for nested sections and query-driven pages, with aria-current set, and choose match options per link rather than one global habit.

for a principal

Encourage a shared navigation component that encodes the matching policy once, so active states and accessibility stay consistent across teams.

## What the directive does `RouterLinkActive` is the `@angular/router` directive that adds CSS classes to an element while a link is active. You give it one or more class names, `routerLinkActive="active"` or an array, and it: - finds the `RouterLink` on the same element and any `RouterLink`s among its **descendants**, so it can sit on an `<li>` that wraps the anchor; - after each `NavigationEnd`, asks whether any of those links' `UrlTree`s is active and adds or removes the classes; - emits `isActiveChange` with the new boolean when the state flips; - is exported as `routerLinkActive`, so `#rla="routerLinkActive"` gives the template `rla.isActive`. ## Why Home is always active The check compares the link's URL tree with the router's current URL tree using **match options**. The default is a subset match: | Option | Default | Meaning | |---|---|---| | `paths` | `'subset'` | the current URL may have extra segments beyond the link's | | `queryParams` | `'subset'` | the link's query params must be present; extra ones are allowed | | `matrixParams` | `'ignored'` | matrix params are not compared | | `fragment` | `'ignored'` | the fragment is not compared | A link to `/` has no segments, so every URL contains it, and Home stays highlighted on `/products`, `/orders/7` and everywhere else. The same effect makes a `/products` tab active on `/products/42`, which is usually what you want for a section tab. ## Two ways to tighten it 1. **`{exact: true}`** - shorthand for `paths: 'exact'`, `queryParams: 'exact'`, matrix params and fragment ignored. Home is active only on `/`. The catch: query params must match exactly too, so a Products tab linking to `/products` turns **off** on `/products?page=2`. 2. **An `IsActiveMatchOptions` object** - set each axis yourself. For a tab that should ignore paging and filters: ```html <a routerLink="/products" routerLinkActive="active" [routerLinkActiveOptions]="{paths: 'exact', queryParams: 'ignored', matrixParams: 'ignored', fragment: 'ignored'}"> Products </a> ``` In the object form `paths` is `'exact'` or `'subset'`; `queryParams` and `matrixParams` are `'exact'`, `'subset'` or `'ignored'`; `fragment` is `'exact'` or `'ignored'`. For pager links the reverse is useful: `queryParams: 'exact'` highlights only the link for the current page. ## Accessibility A class is visual only. Pass `ariaCurrentWhenActive="page"` (or `'step'`, `'location'`, `'date'`, `'time'`, `true`) and the directive also sets `aria-current` while the link is active and removes it otherwise, so screen-reader users hear which item is current. Without that input, the directive does not add `aria-current`. ## Timing and signals The directive recomputes after `NavigationEnd` and when the observed links' inputs change, and it applies the classes in a microtask. For a boolean outside the template, Angular 21.1 added a standalone `isActive(url, router, matchOptions)` function that returns a `Signal<boolean>` using the same defaults; the older `Router.isActive()` method is deprecated in its favour. ## Choosing options per link | Link | Suggested options | Why | |---|---|---| | Home `/` | `{exact: true}` | the root is contained in every URL | | Section tab `/products` | default subset, or `queryParams: 'ignored'` | stay active on detail pages and paged lists | | Pager link `?page=3` | `{paths: 'exact', queryParams: 'exact', matrixParams: 'ignored', fragment: 'ignored'}` | only the current page is highlighted | | In-page anchor `#price` | `fragment: 'exact'` | the fragment is ignored otherwise | A frequent bug report, "the menu highlights two items", is almost always Home with the subset default plus the real section, so start there. ## Common interview follow-through - Put `routerLinkActive` on the `<li>` when the styling targets the list item. - Use `{exact: true}` for Home and for leaf links; keep the subset default for section tabs. - Reach for the object form as soon as query params are part of the page state.

  • Why does a Products tab with {exact: true} switch off on /products?page=2?
    The exact shorthand compares query params exactly as well as paths, and the link has no page param. Use the IsActiveMatchOptions object with queryParams set to 'ignored' so paging and filters do not affect the tab.
  • How do you read a link's active state in the template, for example to show an icon?
    Export the directive with #rla="routerLinkActive" and read rla.isActive, or listen to the isActiveChange output. In code, the isActive() function from @angular/router returns a Signal<boolean> for any URL.

saying these in an interview costs you the question

  • Believing routerLinkActive compares the link and URL exactly by default
  • Thinking {exact: true} compares only paths and ignores query parameters
  • Assuming routerLinkActive must sit on the same element as routerLink
  • Expecting a CSS class alone to tell screen readers the current page
  • Recommending Router.isActive() as the current API for new code