254

I am trying to update (add, remove) queryParams from a component. In angularJS, it used to be possible thanks to :

$location.search('f', 'filters[]'); // setter
$location.search()['filters[]'];    // getter

I have an app with a list that the user can filter, order, etc and I would like to set in the queryParams of the url all the filters activated so he can copy/paste the url or share it with someone else.

However, I don't want my page to be reloaded each time a filter is selected.

Is this doable with the new router?

Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
Olivier
  • 3,121
  • 2
  • 20
  • 28

13 Answers13

499

You can navigate to the current route with new query params, which will not reload your page, but will update query params.

Something like (in the component):

import {ActivatedRoute, Router} from '@angular/router';
constructor(
    private router: Router,
    private activatedRoute: ActivatedRoute,
) { }

public myMethodChangingQueryParams() {
  const queryParams: Params = { myParam: 'myNewValue' };

  this.router.navigate(
    [], 
    {
      relativeTo: this.activatedRoute,
      queryParams, 
      queryParamsHandling: 'merge', // remove to replace all query params by provided
    });
}

Note, that whereas it won't reload the page, it will push a new entry to the browser's history. If you want to replace it in the history instead of adding new value there, you could use { queryParams: queryParams, replaceUrl: true }.

EDIT: As already pointed out in the comments, [] and the relativeTo property was missing in my original example, so it could have changed the route as well, not just query params. The proper this.router.navigate usage will be in this case:

this.router.navigate(
  [], 
  {
    relativeTo: this.activatedRoute,
    queryParams: { myParam: 'myNewValue' },
    queryParamsHandling: 'merge'
  });

Setting the new parameter value to null will remove the param from the URL.

dman
  • 10,406
  • 18
  • 102
  • 201
Radosław Roszkowiak
  • 6,381
  • 3
  • 15
  • 27
  • 42
    I had to use `[]` instead of `['.']` to make it work – Jaime Gómez Dec 07 '17 at 17:10
  • 5
    Need to use queryParams['relativeTo'] = this.activatedRoute; to make the navigation relative to the current page – klonq Jan 01 '18 at 15:10
  • 5
    Nice solution for solving the OP's problem. What it doesn't get you, unless I'm missing something, is the ability to go backward and forward in your history of the current page (e.g. changed filter or order 5 times, each having own URL but same component) and have the page's content appear how it would if you accessed those 5 URLs directly. I think you could manually accomplish this via `this.activatedRoute.queryParams.subscribe` and do various updates in your component, but is there a simple Angular way to just load the route such that back and forward would work automatically? – jmq Mar 22 '18 at 21:33
  • 11
    this actually destroys the component and calls ngOnit() everytime for change in url param. Is there a way to avoid calling ngOnit() ? – undefined Sep 25 '18 at 09:22
  • @undefined, it should neither destroy component nor call ngOnit. I've just confirmed a moment ago on angular 6.0.7. I think either something has changed here with a minor angular update (a bit unlikely) or something else destroys your component. – Radosław Roszkowiak Sep 25 '18 at 17:15
  • @RadosławRoszkowiak so this is what my question is: https://stackoverflow.com/questions/52494548/how-to-change-the-url-without-destroying-the-component-angular5 will be very thankful if you throw some pointers. – undefined Sep 25 '18 at 17:49
  • 2
    when using router.navigate it will scroll top of the window, any ideas how to disable this? – Hese Oct 12 '18 at 08:14
  • 1
    @Hese, are you sure? I don't think it will scroll to the top. Please take a look at this example: https://stackblitz.com/edit/angular-sdtsew?file=src/app/hello.component.ts – Radosław Roszkowiak Oct 12 '18 at 18:13
  • For Angular 6, Yeah I have the same issue as @Hese because when you set `{scrollPositionRestoration: 'enabled'}` for `RouterModule` then it pushes to top and most times it is desired but I have a case where I need it disabled for one place. – rain01 Mar 13 '19 at 20:57
  • 1
    Where did you define `activatedRoute`? – Mohammad Kermani Sep 04 '19 at 14:14
  • 1
    @RadosławRoszkowiak this solution is not working if I'm assigning array of values in query params. If initially I have assigned queryParams a value of [0] and then changed it to [0,1], URL shows only [0] value. Click on "About" in this example - https://stackblitz.com/edit/angular-6-routing-dbhpbl, on every click I'm appending new value into the array (see console), but URL is not getting updated. If I navigate to different page (click service or dashboard link), it'll update the URL with all those values – Hemendra Oct 09 '19 at 09:47
  • If i am using this page is reloading but i would like to change queryparams without reolading. Kindly let me know how to do it. Thanks in advance. – Santhosh Mar 03 '20 at 11:31
  • Maybe this is helpful for others: In my case this didn't work either, as the current route was matched with `path: '**'`. As soon as I assigned a system route to it, the code shown here worked. – dude Sep 16 '20 at 16:02
  • could you please add more details on where you define "activatedRoute" – Kurkula Jun 02 '21 at 17:37
  • You didnt define `activatedRoute` in your constructor: `private activatedRoute: ActivatedRoute`. Also when having `scrollPositionRestoration` activated, the page scroll to the top but it shouldnt. – Mick Oct 25 '21 at 09:42
  • This still fires a navigation event in the router – Cesar Aug 09 '23 at 13:11
52

@Radosław Roszkowiak's answer is almost right except that relativeTo: this.route is required as below:

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

changeQuery() {
    this.router.navigate(['.'], { relativeTo: this.route, queryParams: { ... }});
}
rofrol
  • 14,438
  • 7
  • 79
  • 77
Robert
  • 1,964
  • 1
  • 22
  • 22
26

In Angular 5 you can easily obtain and modify a copy of the urlTree by parsing the current url. This will include query params and fragments.

  let urlTree = this.router.parseUrl(this.router.url);
  urlTree.queryParams['newParamKey'] = 'newValue';

  this.router.navigateByUrl(urlTree); 

The "correct way" to modify a query parameter is probably with the createUrlTree like below which creates a new UrlTree from the current while letting us modify it using NavigationExtras.

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

constructor(private router: Router) { }

appendAQueryParam() {

  const urlTree = this.router.createUrlTree([], {
    queryParams: { newParamKey: 'newValue' },
    queryParamsHandling: "merge",
    preserveFragment: true });

  this.router.navigateByUrl(urlTree); 
}

In order to remove a query parameter this way you can set it to undefined or null.

bjornalm
  • 399
  • 3
  • 6
  • 2
    `navigateByUrl` is diffreent to `navigate` - not just with the UrlTree param. See https://stackoverflow.com/a/45025432/271012 – Drenai Feb 15 '18 at 21:00
21

The answer with most vote partially worked for me. The browser url stayed the same but my routerLinkActive was not longer working after navigation.

My solution was to use lotation.go:

import { Component } from "@angular/core";
import { Location } from "@angular/common";
import { HttpParams } from "@angular/common/http";

export class whateverComponent {
  constructor(private readonly location: Location, private readonly router: Router) {}

  addQueryString() {
    const params = new HttpParams();
    params.append("param1", "value1");
    params.append("param2", "value2");
    this.location.go(this.router.url.split("?")[0], params.toString());
  }
}

I used HttpParams to build the query string since I was already using it to send information with httpClient. but you can just build it yourself.

and the this._router.url.split("?")[0], is to remove all previous query string from current url.

moi_meme
  • 9,180
  • 4
  • 44
  • 63
  • 9
    This SHOULD be the accepted answer, the router.navigates also refreshes the ngOnit, location.go does NOT do this! This is really important because if your component does some ngOnit logic (HTTP calls etc.) they will get called again when you use router.navigate. You don't want this. – Danny Hoeve Mar 23 '19 at 00:20
  • Why do you need router, for url only? Isn't `this.location.path().split...` better? – Vladimir Apr 18 '19 at 08:30
  • @Vladimir you may be right, I was using it for other purpose in my component, so it was not only for that. I do not have time to validate though. – moi_meme Apr 18 '19 at 19:25
  • This will not work if you want to preserve query parameters, for instance promotion or google tracking parameters – Jon Tinsman Jun 18 '19 at 23:29
  • If anyone would like to change url without pushing it to history, use `location.replaceState` instead. – Michał Stochmal Oct 13 '20 at 15:00
  • 1
    That is the only answer that worked for me, and it makes sense actually. – spinner Jan 11 '21 at 11:17
15

Try

this.router.navigate([], { 
  queryParams: {
    query: value
  }
});

will work for same route navigation other than single quotes.

er-sho
  • 9,581
  • 2
  • 13
  • 26
Prudhvi Raj
  • 151
  • 1
  • 2
11

If you want to change query params without change the route. see below example might help you: current route is : /search & Target route is(without reload page) : /search?query=love

    submit(value: string) {
      this.router.navigate( ['.'],  { queryParams: { query: value } })
        .then(_ => this.search(q));
    }
    search(keyword:any) { 
    //do some activity using }

please note : you can use this.router.navigate( ['search'] instead of this.router.navigate( ['.']

Owen Kelvin
  • 14,054
  • 10
  • 41
  • 74
Azeem Chauhan
  • 407
  • 5
  • 8
  • I'm not sure that the [.] is a better approach than `relativeTo:`, but the `.then()` was helpful - was not aware that navigate() returned a promise, so glad you posted! – Drenai Mar 03 '18 at 10:59
  • 2
    Instead of using ['.'] or ['search'], we can simply use [] – Sahil Bhatia Apr 26 '21 at 11:52
8

I ended up combining urlTree with location.go

const urlTree = this.router.createUrlTree([], {
       relativeTo: this.route,
       queryParams: {
           newParam: myNewParam,
       },
       queryParamsHandling: 'merge',
    });

    this.location.go(urlTree.toString());

Not sure if toString can cause problems, but unfortunately location.go, seems to be string based.

Gaurav Paliwal
  • 1,556
  • 16
  • 27
Razze
  • 4,124
  • 3
  • 16
  • 23
7

Better yet - just HTML:

<a [routerLink]="[]" [queryParams]="{key: 'value'}">Your Query Params Link</a>

Note the empty array instead of just doing routerLink="" or [routerLink]="''"

Brachacz
  • 156
  • 1
  • 4
6

Angular's Location service should be used when interacting with the browser's URL and not for routing. Thats why we want to use Location service.

Angulars HttpParams are used to create query params. Remember HttpParams are immutable, meaning it has to be chained when creating the values.

At last, using this._location.replaceState to change to URL without reloading the page/route and native js location.path to get the url without params to reset the params every time.

constructor(
    private _location: Location,
) {}

...

updateURLWithNewParamsWithoutReloading() {
    const params = new HttpParams().appendAll({
        price: 100,
        product: 'bag'
    });

    this._location.replaceState(
        location.pathname,
        params.toString()
    );
}
2

First, we need to import the router module from angular router and declare its alias name

import { Router } from '@angular/router'; ---> import
class AbcComponent implements OnInit(){
constructor(
    private router: Router ---> decalre alias name
  ) { }
}

1. You can change query params by using "router.navigate" function and pass the query parameters

this.router.navigate([], { queryParams: {_id: "abc", day: "1", name: "dfd"} 
});

It will update query params in the current i.e activated route

  1. The below will redirect to abc page with _id, day and name as query params

    this.router.navigate(['/abc'], { queryParams: {_id: "abc", day: "1", name: "dfd"} });

    It will update query params in the "abc" route along with three query paramters

For fetching query params:-

    import { ActivatedRoute } from '@angular/router'; //import activated routed

    export class ABC implements OnInit {

    constructor(
        private route: ActivatedRoute //declare its alias name
      ) {}

    ngOnInit(){
       console.log(this.route.snapshot.queryParamMap.get('_id')); //this will fetch the query params
    }
VIKAS KOHLI
  • 8,164
  • 4
  • 50
  • 61
0

I've had an interesting situation where we used only one component for all routes we had. This is what routes looked like:

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    children: [
      { path: 'companies', component: HomeComponent },
      { path: 'pipeline', component: HomeComponent },
      // ...
    ]
  },
  // ...
];

So, basically, paths /, /companies and /pipeline were all having the same component that had to be loaded. And, since Angular prevents reloading of the components if they were previously loaded in the DOM, Router's navigate method returned a Promise that always resolved with null.

To avoid this, I had to use onSameUrlNavigation. By setting this value to 'reload', I managed to make the router navigate to the same URL with the updated query string parameters:

@NgModule({
  imports: [RouterModule.forRoot(routes, { onSameUrlNavigation: 'reload' })],
  exports: [RouterModule]
})
Mladen
  • 2,070
  • 1
  • 21
  • 37
0

Also you can add BehaviorSubject like:

refresher$ = new BehaviorSubject(null);

I changed my code from that:

this.route.queryParamMap.subscribe(some code)

to:

combineLatest([
    this.route.queryParamMap,
    this.refresher$
])
  .pipe(
     map((data) => data[0])
  )
  .subscribe(here is your the same code)

And when you need refresh your subscription, you need call this:

this.refresher$.next(null);

Also don't forget add unsubscribe from that to ngOnDestroy

0

you can navigate to the same url using [] and in extras add the params, finally add replaceUrl: true (replaceUrl Angular Doc) to overrride the last current history.

this.router.navigate([], {
    queryParams: newQueryParams,
    queryParamsHandling: 'merge',
    replaceUrl: true
  });

and if you need to do on every navigation you can use router.events as follow.

this.routerSubscription = this.router.events.subscribe((event: Event) => {
  if (event instanceof NavigationEnd) {
    this.router.navigate([], {
     queryParams: newQueryParams,
     queryParamsHandling: 'merge',
     replaceUrl: true
    });
  }
});