Accessible Angular Routes

  1. Web Accessibility (A11y) in Angular – Introduction
  2. Accessibility Testing Tools for Angular
  3. Accessible Angular Routes
  4. ARIA roles and attributes in Angular

This article explains how to use Angular Router features to achieve quick wins in improving Accessibility (A11y). It is the third edition of our A11y blog series. If you want to enhance your Angular A11y skills, please make sure to read the other articles in this series as well.

Page Titles

Does your Angular App look like this if you open it in multiple tabs?

Angular App without Page Titles

If it does, you should definitely consider adding unique page titles for each route. The very easy-to-use Route.title feature shipped with Angular v14 in 2022, yet many Angular Developers are still not using it 😱. Please take a look and adopt it in your Angular Apps.

Route.title

Use this built-in Router feature to automatically update the page <title> after each successful navigation, enhancing both accessibility and UX for all of us. To enable it in your primary <router-outlet />, you only need to set the title property in your routes configuration array:

export const routes: Routes = [
  {
    path: 'demo',
    title: 'Look how easy it is to use',
    component: DemoComponent,
  },
];

This will update the page title in the browser tab and make it accessible to screen readers.

Page Title Demo

In larger Angular Apps, setting page titles can become inconsistent due to the lack of a common prefix or suffix for routes.

Global Page Title Strategy

To address this, you can extend Angular's abstract TitleStrategy class and implement your custom page title strategy:

// [imports]

@Injectable()
export class PageTitleStrategy extends TitleStrategy {
  private readonly title = inject(Title);

  updateTitle(routerState: RouterStateSnapshot): void {
    const pageTitle = this.buildTitle(routerState);
    if (pageTitle) {
      this.title.setTitle(pageTitle + ' – Demo');
    } else {
      this.title.setTitle('Link to Demo below!');
    }
  }
}

Then add the custom title strategy to your app.config.ts:

// [imports]
import { PageTitleStrategy } from "./page-tite-strategy";

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(withIncrementalHydration()),
    provideExperimentalZonelessChangeDetection(),
    provideRouter(routes),
    { provide: TitleStrategy, useClass: PageTitleStrategy }, // add this line
  ],
};

Dynamic Page Titles using Angular Router Params

To create dynamic page titles using Angular Router parameters, follow these steps:

  1. First, we set up the withComponentInputBinding() function to bind the route parameters to your component inputs:
// [imports]

export const appConfig: ApplicationConfig = {
  providers: [
    provideClientHydration(withIncrementalHydration()),
    provideExperimentalZonelessChangeDetection(),
    provideRouter(routes, withComponentInputBinding()), // add feature to router
    { provide: TitleStrategy, useClass: PageTitleStrategy },
  ],
};
  1. Then, we add the parameter to the route definition:
export const routes: Routes = [
  // [...]
  {
    path: 'demo/:id',
    title: 'Demo #id', // note that this will be ignored and replaced by the dynamic title
    loadComponent: () => import('./demo/demo.component'),
  },
];
  1. Now, we can simply add a input signal to the component that will automatically receive the route param (thanks to the withComponentInputBinding() feature):
// [imports & decorator]
export class DemoComponent {
  readonly id = input<number | undefined>();

  // [...]
}

export default DemoComponent;
  1. Finally, we can use an effect on our id input signal to display the dynamic page title:
// [imports & decorator]
export class DemoComponent {
  readonly id = input<number | undefined>();
  private readonly title = inject(Title);

  constructor() {
    effect(() => this.title.setTitle(this.id() !== undefined ? Page #${this.id()} - Demo : 'Page - Demo'));
  }
}

export default DemoComponent;

Now, when you navigate to /demo/1, the page title will be set to Page #1 - Demo.

All Together Now

To bring it all together – with the global page title strategy – we add another method to the PageTitleStrategy class and provide it in root:

// page-title-strategy.ts
import { RouterStateSnapshot, TitleStrategy } from '@angular/router';
import { inject, Injectable } from '@angular/core';
import { Title } from '@angular/platform-browser';

@Injectable({
  providedIn: 'root',
})
export class PageTitleStrategy extends TitleStrategy {
  private readonly title = inject(Title);

  updateTitle(routerState: RouterStateSnapshot): void {
    this.setTitle(this.buildTitle(routerState));
  }

  setTitle(pageTitle?: string): void {
    if (pageTitle) {
      this.title.setTitle(${pageTitle} – Demo);
    } else {
      this.title.setTitle('Page title like a pro');
    }
  }
}

And here is the final version of the dynamic page title effect in the DemoComponent using our injected PageTitleStrategy:

// demo.component.ts
// [imports & decorator]
export class DemoComponent {
  private readonly pageTitleStrategy = inject(PageTitleStrategy);

  readonly id = input<number | undefined>();

  constructor() {
    effect(() => this.pageTitleStrategy.setTitle(this.id() !== undefined ? Page #${this.id()} : 'Page'));
  }
}

export default DemoComponent;

Now you can set the page title depending on the route params and have a consistent suffix for all routes in your Angular App.

Dynamic Page Title Demo

Pretty nice, huh? Check out the full source code in the title-strategy branch of my GitHub repo.

RouterLinkActive

The RouterLinkActive directive is another powerful and lightweight tool for indicating the active state of navigation links in your Angular App. It allows you to apply CSS styles to navigation links based on their active state, making it easier to create appealing and accessible menus.

<!-- nav.component.html -->
<nav>
  <a [routerLink]="demo" routerLinkActive="active">Demo</a>
  <!-- ... -->
</nav>
<!-- nav.component.scss -->
nav a.active {
  color: var(--nav-link--active__color);
  text-decoration: none;
}

Fine-tuned control with routerLinkActiveOptions

Since Angular v14, you can use the routerLinkActiveOptions directive to fine-tune the active state of your links. Where options will have either the shape of IsActiveMatchOptions:

export declare interface IsActiveMatchOptions {
  fragment: 'exact' | 'ignored';
  matrixParams: 'exact' | 'subset' | 'ignored';
  paths: 'exact' | 'subset';
  queryParams: 'exact' | 'subset' | 'ignored';
}

Or, just a boolean named exact:

{
  exact: boolean
}

To apply exact matching, add the routerLinkActiveOptions directive to your link:

<!-- nav.component.html -->
<nav>
  <a
    routerLink="/demo"
  routerLinkActive="active"
  [routerLinkActiveOptions]="{ exact: true }"
  >
  Demo
  </a>
  <!-- ... -->
</nav>

Indicate current page ariaCurrentWhenActive

A final quick win in A11y in Angular is to highlight the current page link in the nav for screen readers via aria-current: We need to add the aria-current="page" attribute. This can easily be done using the ariaCurrentWhenActive input on the routerLinkActive directive by setting its value to "page":

<!-- nav.component.html -->
<nav>
  <a
    routerLink="/demo"
    routerLinkActive="active"
    [routerLinkActiveOptions]="{ exact: true }"
    ariaCurrentWhenActive="page"
  >
    Demo
  </a>
  <!-- ... -->
</nav>

Accessibility Workshop

For those looking to deepen their Angular expertise, we offer a range of workshops – both in English and German:

Conclusion

Implementing Angular's built-in router features is a simple yet powerful way to boost your A11y 🚀

By leveraging dynamic page titles, a global title strategy, and fine-tuning active link indicators with RouterLinkActive and ariaCurrentWhenActive, you can create a more inclusive UX that benefits everyone – from seasoned Devs (like me 😂) to users who rely on assistive technologies. These strategies not only improve UX but also help standardize navigation and page management across all Angular Apps. Back to our example from the beginning, now our browser tab bar looks like this:

Angular App with Page Titles

I think this is a great example of a quick win! Keep playing with these features and explore the rest of our A11y blog series for more insights on building accessible, high-performing web apps.

In the next edition of our A11y blog series, we'll cover more ARIA roles and attributes.

This blog post was written by Alexander Thalhammer. Follow me on Linkedin, X or giThub.

References

eBook: Modern Angular

Stay up to date and learn to implement modern and lightweight solutions with Angular’s latest features: Standalone, Signals, Build-in Control Flow.

Free Download