108

I wrote an Angular2 (v2.0.1) application that makes use of the router. The website is loaded with several query string parameters, so the full URL initially looks like this:

https://my.application.com/?param1=val1&param2=val2&param3=val3

In my route configuration, I have an entry which redirects an empty route:

const appRoutes: Routes = [
    {
        path: '',
        redirectTo: '/comp1',
        pathMatch: 'full'
    },
    {
        path: 'comp1',
        component: FirstComponent
    },
    {
        path: 'comp2',
        component: SecondComponent
    }
];

My problem is, that after the app has been bootstrapped, the URL does not contain the query parameters anymore, instead it looks like this:

https://my.application.com/comp1

Is there any way I can configure the router so that it keeps the initial query string when navigating?

Thank you
Lukas

Freestyle09
  • 4,894
  • 8
  • 52
  • 83
Lukas Kolletzki
  • 2,126
  • 3
  • 22
  • 30

10 Answers10

99

I don't think there is a way to define that in the routes configuration.

Currently it is supported for routerLinks and imperative navigation to enable

You can add a guard to the empty path route, where in the guard navigation to the /comp1 route is done.

router.navigate(['/comp1'], { preserveQueryParams: true }); //deprecated. see update note
router.navigate(['/comp1'], { queryParamsHandling: "merge" });

There is a PR to allow to configure preserveQueryParams globally.

Update note: from https://angular.io/api/router/NavigationExtras, preserveQueryParams is deprecated, use queryParamsHandling instead

Falko
  • 17,076
  • 13
  • 60
  • 105
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • 1
    Where would that line of code be added? And can you explain "guard" with a code example? – mattrowsboats Mar 05 '17 at 02:41
  • 1
    I did this, but adding a canActivate guard to the empty string "redirect" route, is not picked up. – mattrowsboats Mar 05 '17 at 19:29
  • You need to configure a dummy component and redirect in the guard – Günter Zöchbauer Mar 05 '17 at 20:57
  • 2
    Ok, but the problem still persists on app entry. If you're trying to "catch" the url parameters after the app has been bootstrapped, it's too late. They've already been stripped. – mattrowsboats Mar 07 '17 at 19:49
  • You need to capture them before the first redirect or navigation. – Günter Zöchbauer Mar 07 '17 at 19:52
  • @GünterZöchbauer How would I do that? I have the same problem - the query params are already gone, when I am trying to use them in the canActivate guard on app entry. – Christian Ulbrich May 04 '17 at 11:48
  • You could try `router.events.subscribe()` and try to read them in the first `NavigationStart` event. (haven't tried myself) – Günter Zöchbauer May 04 '17 at 11:52
  • `export class AppComponent { constructor(public router: Router) { let origFcn = this.router.navigateByUrl.bind(this.router); this.router.navigateByUrl = (url: string | UrlTree, extras?: NavigationExtras | undefined) => { extras = extras || {}; extras.queryParams = {"myKey":"myValue"}; extras.queryParamsHandling = "merge"; return origFcn(url,extras); }; }` does overwrite the `navigateByUrl` function and `Router` instance that the global `RouterLink` directive uses, but the hardcoded extras aren't applying. – Michelle Norris Jan 16 '18 at 15:49
  • @GünterZöchbauer where does the router.events.subscribe has to be called. When I use it it is already too late, the route has already navigated and queryParams are empty. – maidi Mar 05 '18 at 12:42
  • @maidi If you configure a root level redirect route, there is currently no way. You need to redirect "manually" by redirecting to a dummy component where you can fetch the queryParams and redirect to another router where you pass the queryParams along. I don't know if that is actually your use case. – Günter Zöchbauer Mar 05 '18 at 12:53
  • 2
    After long time, I found that `navigateByUrl` does not work. It is better to use `navigate` always. – Karthik Mar 05 '18 at 13:07
  • 1
    I'm sorry to downvote, but only AArias answer worked for me (Angular 9, if that's relevant). – Arnaud P Mar 10 '20 at 08:09
70

If you are navigating using HTML template then you can use

<a [routerLink]="['/page-2']" [routerLinkActive]="['is-active']" queryParamsHandling="merge">

Something to watch out for is that queryParamsHandling param is without the square brackets.

Kamalpreet
  • 1,043
  • 9
  • 11
  • 3
    this is the best answer and the remark about the brackets really saved me some time – Alas Mar 24 '18 at 18:45
  • The HTML solution is what I was really looking for. Thanks! – Dinesh Shekhawat Mar 02 '20 at 08:04
  • 2
    you can use with square brackets: [queryParamsHandling]="'merge'" that's angular basics, brackets mean 'computation needed' so instead of "merge" you need to write string in "": "'merge'" – Yura Dec 10 '20 at 14:17
39

It turns out the undocumented way to do this without other hacks is to simply remove the leading slash in the "redirectTo" field. Since you are matching the full path you can have the certainty that it'll do what you want (i.e. no surprise url segments) and since it's no longer an absolute target, Angular will preserve the current query parameters.

So in this case

{
  path: '',
  redirectTo: '/comp1',
  pathMatch: 'full'
}

becomes:

{
  path: '',
  redirectTo: 'comp1',
  pathMatch: 'full'
}

Source: https://github.com/angular/angular/issues/13315

Christopher
  • 856
  • 9
  • 16
15

Günter Zöchbauer's answer should work properly but, for some reason, it is not working for me at all. What did end up working was passing the queryParams directly instead of 'preserving' them.

This is what my guard looks like:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    (...)
    this.router.navigate(['login'], { queryParams: route.queryParams });
}
AArias
  • 2,558
  • 3
  • 26
  • 36
4

In Angular 10 now you can use as follows:

    import { ActivatedRoute, Router } from '@angular/router';

class Component {
    constructor(private route: ActivatedRoute, private router: Router) {}

    someMethod() {
        //You can use either merge or preserve to keep the query params
        this.router.navigate(['/'], { queryParamsHandling: 'preserve' })

    }
}

 
Balu
  • 398
  • 6
  • 15
3

You may want to search https://github.com/angular/angular/issues for a feature request similar to this. If none exists, submit a feature request.

In the mean time: I believe you will need to create a component, on the path: '', with the sole purpose of then redirecting to '/comp1' while preserving the QueryString params.

Martin
  • 15,820
  • 4
  • 47
  • 56
2

After having had a go at most answers, I found that

  • Günter Zöchbauer's answer doesn't work for me at all
  • Christopher's suggestion of removing the leading / didn't do it either
  • AArias' answer did work but lead to the adding of two urls in the history:
    1. https://my.application.com/comp1?param=val <= ( ಠ 益ಠ )
    2. https://my.application.com/comp1;param=val

So here's yet another approach, that eventually behaved as per my expectations:

import { ActivatedRoute, Router } from '@angular/router';

class Component {
    constructor(private route: ActivatedRoute, private router: Router) {}

    someMethod() {
        router.navigate(['/comp1', this.route.snapshot.params]);
    }
}
Arnaud P
  • 12,022
  • 7
  • 56
  • 67
1

There is a workaround using secondary routes as Angular will persist these across primary route navigation.

First, add a named router outlet in your top component:

<router-outlet name="params"><router-outlet>

Next, create a dummy component to route to:

@Component({
    template: ""
})
export class ParamsComponent {}

and define a route to instantiate this component into the named outlet:

{
    path: ':val1',
    component: ParamsComponent,
    outlet: "params"
}

Change you app navigation to:

https://my.application.com/(params:val1)

If you look at any ActivatedRoute, you can find the "params" route using:

  var paramsRoute = this.activatedRoute.route.children.find(r => r.outlet == "params");

If paramsRoute is null, the url doesn't contain the (params:val1).

This next part gets a bit "hacky" as the secondary route is instantiated after the primary route on initial load. Because of this, until your app is fully loaded, you may find paramsRoute.snapshot to be null. There is a private property "_futureSnapshot" which will contain the route params on initial startup...and persists through the life of the app. You can get to these by using:

var queryParams = 
      paramsRoute
      ? paramsRoute["_futureSnapshot"].params
      : {};
var val1 = queryParams["val1"];

Given that _futureSnapshot is not part of the public API, this is probably a field we're not supposed to use. If you feel icky using it, you could probably subscribe to paramsRoute.params, but this will probably complicate your components.

if (paramsRoute) {
    paramsRoute.params.subscribe(params => {
        this.queryParams = params;
        this.loadData();
    });
} else {
    this.queryParams = {};
    this.loadData();
}

========= AMENDMENT =============

I found an even better way to pull the query parameters which is definitely NOT icky... In a component or service which is instantiated before routing occurs, add the following logic:

    const routeRecognizedSubscription = this.router.events
        .filter(e => e instanceof RoutesRecognized)
        .subscribe((e: RoutesRecognized) => {
            const paramsRoute = e.state.root.children.find(r => r.outlet == "params");
            if (paramsRoute) {
                // capture or use paramsRoute.params 
            }
            routeRecognizedSubscription.unsubscribe();
        });

This code temporarily subscribes to RoutesRecognized events which occur before navigation. After it receives the first event, it will automatically unsubscribe as we only need to do this when the app starts.

On the first event, we look for the state corresponding to "params" outlet. If found, the params property will contain the data we need. No need to access private properties.

0

If you are navigating using HTML template, you can also use preserveQueryParams="true"

Notice that preserveQueryParams is without a square bracket.

Eg:

<a [routerLink]="['/navigate-to']" preserveQueryParams="true">

Vandesh
  • 6,368
  • 1
  • 26
  • 38
-3

capture the original url in -base href-

https://example.com/order?id=123

then it will presist

https://example.com/order?id=123#/product

dot52
  • 5
  • 1