A Quick Note on Skip-Links in Angular

Stephen Belyea
3 min readMar 23, 2018

--

Edit (Aug ‘18): Sadly, this fix may no longer work in Angular 5.2, as found by Yitz Schaffer. Bummer.
Edit (Apr ‘19): Looks like this may be working again, as of 7.2. Thanks to Andy Pickler for the update!

For reference, a “skip-link” is a visually-hidden anchor element used to skip repetitive blocks (eg. main navigation) throughout a website. Using a combination of styling properties (opacity, z-index, text-indent, etc.), the anchor is typically “hidden” (read: not visible to the eye) until it receives keyboard focus. They’re pretty helpful — for you* and your users.

“Skip this page navigation” link appears only on focus, to skip past repetitive navigation blocks. From the gov’t of Ontario’s Accessibility information overview: https://www.ontario.ca/page/accessibility

Generally, the skip-link has an href set to the id of whatever content we’re meant to skip to (eg. #main-content). Triggering the anchor will shift the page (and user’s context) to the provided element. For example:

<div>
<a href="#main-content">Skip to main content</a>
<nav>
...
</nav>
<main id="main-content">
...
</main>
</div>

While working on a project in Angular (4x + Router), I recently had some trouble with getting the anchor’s href to link correctly (eg. website.com/the-page#main-content). Without directly adding the page’s path string to the attribute, the anchor would resolve to the root domain + #main (ignoring the page’s path) when triggered (eg. website.com/#main-content). Seeing as the main navigation was a global component (as one is wont to build when employing a JavaScript framework), adding a single path’s string wasn’t an option.

The particular app I’ve been working on uses Angular’s Router package for managing in-site navigation. So, next came an attempt with the routerLink property (as a replacement for the anchor’s href). Alas, this treated the trigger as a navigation event and still didn’t resolve correctly. It also URL-encoded the # character. Bummer.

Why not just use a button?

“Why not just use a button?”, you may be asking at this point. That’s a fair question, given the challenges at hand. First, the skip-link is technically navigation (triggering it takes you to different content), while buttons should be reserved for interaction (eg. opening an accordion panel). Plus, why would I throw in the towel on something that’s so incredibly simple with HTML (a not-infrequent situation when dealing with a JS framework)!?

The next (and final) attempt made use of the Router package, imported into the navigation component. From there, the Router’s url property was used to build the current page’s link into a template string. Append the #main-content string to the path, and we wind up with a variable that can be used to bind the anchor’s href. Binding to the href (instead of routerLink) allows the anchor to trigger simple DOM navigation without firing off a Router change event.

I’m still not in love with the workaround process, but at least the result accomplishes the task:

@import { Component, OnInit } from '@angular';
@import { Router } from '@angular/router';
@Component({
selector: 'app-main-nav',
template: `
<a [href]="skipLinkPath">Skip to main content</a>
<nav> ... </nav>
`
})
export class MainNavComponent implements OnInit {
skipLinkPath: string;
constructor(
private router: Router
) { }
ngOnInit() {
this.skipLinkPath = `${this.router.url}#main-content`;
}
}

TL;DR: Just copy/paste the snippet above.

*Depending on the scope, timeline, and jurisdiction of your project, skip-links (or a similar solution) may be a core requirement.

--

--

Stephen Belyea

Husband, father, habitual dork, front end developer + accessibility instigator, attempted writer, ex-pat Maritimer.