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?
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.
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:
- 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 },
],
};
- 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'),
},
];
- Now, we can simply add a
input
signal to the component that will automatically receive the route param (thanks to thewithComponentInputBinding()
feature):
// [imports & decorator]
export class DemoComponent {
readonly id = input<number | undefined>();
// [...]
}
export default DemoComponent;
- Finally, we can use an
effect
on ourid
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.
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:
- ♿ Accessibility Workshop
- 📈 Best Practices Workshop (including accessibility related topics)
- 🚀 Performance Workshop
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:
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
- GitHub repo of demo, by Alexander Thalhammer
- Angular v14 – official blog post by Emma Twersky
- Tweet about Route title by Emma Twersky
- Page Titles With The Router by Brandon Roberts
- Router Link Accessibility Features by Brian Treese
- Make it accessible: Navigation in Angular by Daniel Marin
- ariaCurrentWhenActive – official docs