Implementing a custom angular location strategy

Context

Angular provides two approaches for routing, called Location Strategies. They are the HashLocationStrategy and the PathLocationStrategy. One key difference is that the HashLocationStrategy puts a # in all component URLs, whereas the PathLocationStrategy does not. This means that it's a breaking change to move from one to the other, because any bookmarks your users have will stop working.

I was recently working with an application which had been set up using the HashLocationStrategy. I implemented an OAuth 2 login process and needed a callback URL which should be within the angular app. However, the OAuth 2 specification does not allow callback URLs to include a # character.

I didn't want to break users' bookmarks by changing to use the PathLocationStrategy, but I also needed a URL which didn't contain a # for one specific component. What could I do which would meet both these requirements?

The solution

I decided to implement my own CustomHashLocationStrategy, based on the built-in HashLocationStrategy. The callback route will not have a #, and the CustomHashLocationStrategy is responsible for detecting this route and adding the # to the front of it. This is the only custom behaviour, it is otherwise identical to HashLocationStrategy. This way, existing bookmarks will continue to work, because the app is still using the HashLocationStrategy.

Code

This is the class I wrote. The only custom logic it contains is a single if statement.

import { APP_BASE_HREF, HashLocationStrategy, PlatformLocation } from '@angular/common';
import { Inject, Injectable, Optional } from '@angular/core';

@Injectable()
export class CustomHashLocationStrategy extends HashLocationStrategy {
    constructor(private platformLocation: PlatformLocation,
                @Optional() @Inject(APP_BASE_HREF) _baseHref?: string) {
        super(platformLocation, _baseHref);
    }

    path(includeHash: boolean = false): string {
        let path = this.platformLocation.hash;
        if (path == null) {
            path = '#';
        }

        // This if block is the only custom code in this class.
        // The rest is the same as the HashLocationStrategy.
        // https://github.com/angular/angular/blob/13.3.2/packages/common/src/location/hash_location_strategy.ts#L65
        if (location.pathname === '/path-to-callback') {
            path = `#${location.pathname}/${location.search}`;
        }

        return path.length > 0 ? path.substring(1) : path;
    }
}