1

I've got an Angular 10 app which has to be called with an initial query parameter:

http://localhost:4200/?initialqueryparam=somevalue

I'm using hash location strategy, so further routes are like this:

http://localhost:4200/#/subroute/...?optionalqueryparamforspecificroute=anothervalue

What I need is to keep the initial query params for all further routes in the application. The url therefore should look like this:

http://localhost:4200/?initialqueryparam=somevalue/#/subroute/...?optionalqueryparamforspecificroute=anothervalue

Is this possible and if yes: how?

Update 1:

In the meantime I've implemented this hack using ngrx store. The disadvantage is, that the query params are not added after the base url but after the subroute:

http://localhost:4200/#/subroute/...?optionalqueryparamforspecificroute=anothervalue&initialqueryparam=somevalue

This has two disadvantages:

  1. My menu items which are styled with the help of the routerLinkActive directive are not styled correctly anymore because the configured route is not recoginzed due to the appended query param
  2. When Right-Clicking on a menu item (browser context menu opens) and selecting "open in new tab" the query params get missed as the ngrx effect is not called in this case
  3. When calling the app with this url, I cannot use window.location.search like I do with the initial url to get the initial query param but I have to dig out the queryparam from window.location.hash with ugly string splitting.

It would be much better if there was a way to add the initial param directly behind the base url. Any ideas?

MatterOfFact
  • 1,253
  • 2
  • 18
  • 49
  • 1
    Does this answer your question? [Angular2 router keep query string](https://stackoverflow.com/questions/39898656/angular2-router-keep-query-string) – mwilson Oct 07 '20 at 14:45

5 Answers5

3

This github answer did the trick for me:

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

@Injectable()
export class HashPreserveQueryLocationStrategy extends HashLocationStrategy {
    private readonly initialQueryParam: string;

    constructor(
        _platformLocation: PlatformLocation,
        @Optional() @Inject(APP_BASE_HREF) _baseHref?: string
    ) {
        super(_platformLocation, _baseHref);
        this.initialQueryParam = window.location.search || '';
    }

    prepareExternalUrl(internal: string): string {
        const hash = super.prepareExternalUrl(internal);
        return this.initialQueryParam + hash;
    }
}

And in app module:

providers: [
    { provide: LocationStrategy, useClass: HashPreserveQueryLocationStrategy }]
MatterOfFact
  • 1,253
  • 2
  • 18
  • 49
2

Ideally, you'd want to just configure some global property directly within your router config that would persist the query params, but I do not believe that is possible (a very rare use case).

What is available however is the ability to persist query params directly within the Router though.

So what you could do is enforce some specific routing rules so that queryParamsHandling is set to preserve when you route to whatever link you're going to.

this.router.navigate(['/view2'], { queryParams: { page: 2 },  queryParamsHandling: "preserve"
});

Additionally, you could potentially automate this entire process if you are using a Navigation Resolver or NgRx Router Store.

So no support for a single switch to flip, but as always, there's a cocktail you can make to fill that void.

https://angular.io/api/router/NavigationExtras#queryParamsHandling

You might also want to check out this PR https://github.com/angular/angular/issues/12664

mwilson
  • 12,295
  • 7
  • 55
  • 95
  • I ended up implementing this hack using ngrx store: github.com/angular/angular/issues/12664#issuecomment-673349277. This works well within the application. Still not working: Open a link in a new tab won't add the query param to the url. Any ideas? – MatterOfFact Oct 08 '20 at 09:17
  • How are you opening the url in a new tab? – mwilson Oct 08 '20 at 15:25
  • right-clicking on a menu item (browser context menu opens) -> open in new tab – MatterOfFact Oct 08 '20 at 16:35
  • Can you update your code? Have you tried doing a console.log or something on page load to see if it's a problem with the query params being set vs angular removing them with the location strategy? – mwilson Oct 08 '20 at 17:39
  • Any further ideas? – MatterOfFact Oct 14 '20 at 07:01
1

I created a package containing an AdvancedRouter and AdvancedRouterLinkDirective that allows you to specify which queryparams should be preserved and which should be dropped: Github repo

Here's the place where it's configured

Pieterjan
  • 2,738
  • 4
  • 28
  • 55
0

If you're using an Angular routing module you could add a child below your initial parameter.

const routes: Routes = [{
  path: ':initialqueryparam',
  component: YourComponent,
  children: [
    {
       path: 'subroute/:optionalqueryparamforspecificroute',
       component YourComponent,
       children: [

       ]
    }
  ]

and in the component:

 this._router.navigate([`${initialParam}/subroute/${optionalqueryparamforspecificroute}`]);
scy
  • 179
  • 1
  • 9
0
@Injectable()
export class PreserveQueryPathLocationStrategy extends PathLocationStrategy {
  private readonly query: string;
  private readonly preservedKeys: string[] = ['affiliate_id']; // this can be extended to be passed from constructor using DI etc.
  constructor(
    _platformLocation: PlatformLocation,
    @Optional() @Inject(APP_BASE_HREF) _baseHref?: string
  ) {
    super(_platformLocation, _baseHref);
    // TODO: fix if use SSR
    this.query = location && location.search ? location.search.substr(1) : '';
  }

  prepareExternalUrl(internal: string): string {
    const path = super.prepareExternalUrl(internal);
    if (!this.query || !Object.keys(this.preservedKeys).length) {
      return path;
    }

    const pathQueryIndex = path.indexOf('?');
    const pathQueryEndWithAndChar = pathQueryIndex === -1 && path.endsWith('&');
    const pathQueryKeyvalues: string[] =
      pathQueryIndex === -1
        ? []
        : this.extractKeyvaluesFromQuery(path.substr(pathQueryIndex + 1));

    const keyvalues = this.extractKeyvaluesFromQuery(this.query);
    const preservedKeyvalues: string[] = [];
    keyvalues.forEach(kv => {
      this.preservedKeys.forEach(pk => {
        if (
          !pathQueryKeyvalues.includes(kv) &&
          (kv === pk || kv.startsWith(`${pk}=`))
        ) {
          preservedKeyvalues.push(kv);
        }
      });
    });
    if (preservedKeyvalues.length) {
      return pathQueryIndex === -1
        ? `${path}?${preservedKeyvalues.join('&')}`
        : `${path}${
            pathQueryEndWithAndChar ? '' : '&'
          }${preservedKeyvalues.join('&')}`;
    } else {
      return path;
    }
  }

  extractKeyvaluesFromQuery(query: string): string[] {
    let keyvalues: string[] = [];
    let queryStr = query;
    while (queryStr.startsWith('&')) {
      queryStr = queryStr.substr(1);
    }
    while (queryStr.endsWith('&')) {
      queryStr = queryStr.substr(0, queryStr.length - 1);
    }
    if (queryStr) {
      keyvalues = queryStr.split('&');
    }
    return keyvalues;
  }
}


   
Milad Jafari
  • 970
  • 10
  • 15