A Neat Trick to Globally Preserve Query Params in Angular Router

A Neat Trick to Globally Preserve Query Params in Angular Router
Photo by Johannes Plenio / Unsplash

The Angular router is a marvel of software engineering. It is based on simple axioms of web navigation.

  • URL is nothing but a serialized tree. This means you can deserialize a URL into a tree that represents it.
  • URL is the single source of truth for a web application (well…it should be!)
  • Routes are a forrest of possible router configurations
  • Each router configuration is a tree that can be serialized into a URL

At the moment there is no way to preserve query params globally on all route navigations. For example, if you were checking for the existence of a token in the URLs to validate if a user can deep link into a URL or not. We can achieve this by Using the age-old Strategy Pattern and Dependency Injection.

Extend Router’s Default Location Strategy

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

@Injectable()
export class PathPreserveQueryLocationStrategy extends PathLocationStrategy {
  
  private get search(): string {
    return this.platformLocation?.search ?? '';
  }
  
  constructor(
    private platformLocation: PlatformLocation,
    private urlSerializer: UrlSerializer,
    @Optional() @Inject(APP_BASE_HREF) _baseHref?: string,
  ) {
    super(platformLocation, _baseHref);
  }
  
  prepareExternalUrl(internal: string): string {
    const path = super.prepareExternalUrl(internal);
    const existingURLSearchParams = new URLSearchParams(this.search);
    const existingQueryParams = Object.fromEntries(existingURLSearchParams.entries());
    const urlTree = this.urlSerializer.parse(path);
    const nextQueryParams = urlTree.queryParams;
    urlTree.queryParams = { ...existingQueryParams, ...nextQueryParams };
    return urlTree.toString();
  }

}

Explanation:

  • We extend the default PathLocationStrategy class to implement our query param preservation algorithm
  • We override the prepareExternalUrl method to handle query params in the URL
  • We Get the existing URLSearch Params as an instance of [URLSearchParams](<https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams>)
  • We create a [URLTree](<https://angular.io/api/router/UrlTree>) from the path and merge the existing and new query params and assign it to the queryParams of the newly created URL tree
  • We serialize our newly created URL Tree and return it as a string


Provide The Custom Location Strategy

// app.module.ts
import { LocationStrategy } from "@angular/common";
import { PathPreserveQueryLocationStrategy } from "./preserve-query-params.ts";
@NgModule({
...
providers: [
    { provide: LocationStrategy, useClass: PathPreserveQueryLocationStrategy }
  ],
...
})
export class AppModule {}

Update TS Config for Dom.iterable Utilities

// tsconfig.json
{
  "compilerOptions": {
		...
    "downlevelIteration": true,
    "lib": [
      "es2019",
      "dom",
      "dom.iterable",
      "ESNext"
    ]
	...
  }
}
tsconfig.json

Caveats

  • Since all query params are preserved, once you add a query param to a route, all subsequent navigations will carry it with them

Conclusion

We learnt how we can globally preserve query params in Angular router. We saw how powerful Angular’s dependency injection system is and we leveraged it to override Angular’s default location strategy.

Here’s a code sandbox with a working example:

angular-preserve-query-params

Thank you for reading and I hope this helps you as it helped me.

I would love to know in the comments below if this was useful!

You can follow me on GitHub or Twitter

Happy Engineering!