简体   繁体   中英

Subscribe to both route params and queryParams in Angular 2

I have set up the following routing system

export const MyRoutes: Routes = [
  {path: '', redirectTo: 'new', pathMatch: 'full'},
  {path: ':type', component: MyComponent}
];

and have the following navigation system

goToPage('new');
goToPageNo('new', 2);

goToPage(type) {
  this.router.navigate([type]);
}
goToPageNo(type, pageNo) {
  this.router.navigate([type], {queryParams: {page: pageNo}});
}

Sample URL looks like this

http://localhost:3000/new

http://localhost:3000/new?page=2

http://localhost:3000/updated

http://localhost:3000/updated?page=5

Sometimes they have optional queryParams (page)

Now I need to read both route params and queryParams

ngOnInit(): void {
  this.paramsSubscription = this.route.params.subscribe((param: any) => {
    this.type = param['type'];
    this.querySubscription = this.route.queryParams.subscribe((queryParam: any) => {
      this.page = queryParam['page'];
      if (this.page)
        this.goToPageNo(this.type, this.page);
      else
        this.goToPage(this.type);
    })
  })
}

ngOnDestroy(): void {
  this.paramsSubscription.unsubscribe();
  this.querySubscription.unsubscribe();
}

Now this is not working as expected, visiting pages without queryParams works, then of I visit a page with queryParams "goToPageNo" gets called multiple times, as I am subscribing to queryParams inside route params.

I looked at the Angular 2 documentation, they do not have any example or codes where a subscription to both route params and queryParams is implemented at the same time.

Any way to do this properly? Any suggestions?

I managed to get a single subscription to both the queryParams and Params by combining the observables by using Observable.combineLatest before subscribing.

Eg.

var obsComb = Observable.combineLatest(this.route.params, this.route.queryParams, 
  (params, qparams) => ({ params, qparams }));

obsComb.subscribe( ap => {
  console.log(ap.params['type']);
  console.log(ap.qparams['page']);
});

For Angular 6+

import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';

...

combineLatest(this.route.params, this.route.queryParams)
    .pipe(map(results => ({params: results[0].xxx, query: results[1]})))
    .subscribe(results => {
        console.log(results);
    });

Where xxx is from your route

{path: 'post/:xxx', component: MyComponent},

Late answer, but another solution : Instead of subscribing to the params and queryparams I subscribe to the NavigationEnd event on the router. Fires only once and both params and queryparams are available on snapshot : (example for angular 4)

this.router.events.filter(event=>event instanceof NavigationEnd)
   .subscribe(event=>{
       let yourparameter = this.activatedroute.snapshot.params.yourparameter;
       let yourqueryparameter = this.activatedroute.snapshot.queryParams.yourqueryparameter;
   });

Regarding unsubscribing : yes it is true routing params, queryParams or navigation events subscriptions are automatically unsubscribed by the router, there is however one exception : if your route has children , when navigating between children, no unsubscribe will occur. Only when you navigate away from the parent route!

Eg I had a situation with tabs as child routes. In constructor of first tab component subscribing on route params to retrieve data. Each time I navigate from first to second tab and back another subscription was added resulting in multiplying the number of requests.

An alternative (on Angular 7+) is to subscribe to the ActivatedRoute url observable and use "withLatestFrom" to get the latest paramsMap and queryParamsMap. It appears that the params are set before the url observable emits:

this.route.url.pipe(
  withLatestFrom(this.route.paramMap, this.route.queryParamMap)
).subscribe(([url, paramMap, queryParamMap]) => {
  // Do something with url, paramsMap and queryParamsMap
});

https://rxjs-dev.firebaseapp.com/api/operators/withLatestFrom

https://angular.io/api/router/ActivatedRoute

The proposed solutions using combineLatest do not work properly. After the first route change, you will get an event every time the param or queryParam value changes, which means you get calls in an intermediate state when one has changed but the other has not, very bad.

@rekna's solution pretty much works for me, except I wasn't getting an event when my app was first loaded, so here's my modification to @rekna's solution:

this.routeUpdate(this.activatedRoute.snapshot);
this.routeSubscription = this.router.events
  .pipe(filter(event => event instanceof NavigationEnd))
  .subscribe(event => this.routeUpdate(this.activatedRoute.snapshot));

Where routeUpdate is a method to do whatever parameter handling is required.

Use ActivatedRouteSnapshot from your ActivatedRoute

ActivatedRouteSnapshot interface has params and queryParams property, and you could get the both value at the same time.

constructor(private route: ActivatedRoute) {
    console.log(this.route.snapshot.params);
    console.log(this.route.snapshot.queryParams);
}

Edit : As OP stated, we only get the initial value of the parameters with this technique.

Example Plunker

I think you should use zip operator https://www.learnrxjs.io/operators/combination/zip.html

Because if you use combineLatest and change url params or query params you got value with new query params and old url params.

Urls for example:

http://localhost/1?value=10

http://localhost/2?value=20

http://localhost/3?value=30

Shifatul's answer extended for ...

  • Angular 9+ (using an array for combineLastest) and
  • simpler syntax (using typescript array parameter decomposition):
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';

// ...

combineLatest([this.route.params, this.route.queryParams])
    .subscribe(([params, queryParams]) => {
        console.log("your route data:", params, queryParams);
    });

I have set up the following routing system

export const MyRoutes: Routes = [
  {path: '', redirectTo: 'new', pathMatch: 'full'},
  {path: ':type', component: MyComponent}
];

and have the following navigation system

goToPage('new');
goToPageNo('new', 2);

goToPage(type) {
  this.router.navigate([type]);
}
goToPageNo(type, pageNo) {
  this.router.navigate([type], {queryParams: {page: pageNo}});
}

Sample URL looks like this

http://localhost:3000/new

http://localhost:3000/new?page=2

http://localhost:3000/updated

http://localhost:3000/updated?page=5

Sometimes they have optional queryParams (page)

Now I need to read both route params and queryParams

ngOnInit(): void {
  this.paramsSubscription = this.route.params.subscribe((param: any) => {
    this.type = param['type'];
    this.querySubscription = this.route.queryParams.subscribe((queryParam: any) => {
      this.page = queryParam['page'];
      if (this.page)
        this.goToPageNo(this.type, this.page);
      else
        this.goToPage(this.type);
    })
  })
}

ngOnDestroy(): void {
  this.paramsSubscription.unsubscribe();
  this.querySubscription.unsubscribe();
}

Now this is not working as expected, visiting pages without queryParams works, then of I visit a page with queryParams "goToPageNo" gets called multiple times, as I am subscribing to queryParams inside route params.

I looked at the Angular 2 documentation, they do not have any example or codes where a subscription to both route params and queryParams is implemented at the same time.

Any way to do this properly? Any suggestions?

Following Shifatul's answer, this supposes working for the observables changed at the same time and just fire once:

import { combineLatest } from 'rxjs';
import { map, debounceTime } from 'rxjs/operators';

...

combineLatest(this.route.params, this.route.queryParams)
    .pipe(map(results => ({params: results[0].xxx, query: results[1]})), debounceTime(0))
    .subscribe(results => {
        console.log(results);
    });

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM