Angular 2 get routeParams in a service
Asked Answered
D

8

14

I want to shift the logic from component to service. But I found out that I can't get the routeParams in a service.

My component looks like

import { Component, OnInit }      from '@angular/core';
import { ActivatedRoute, Params } from '@angular/router';

import { MyService }              from '../services/my.service';

@Component({
  moduleId: module.id,
  templateUrl: 'my.component.html',
  styleUrls: ['my.component.css']
})
export class MyComponent implements OnInit {
  constructor(private myService: MyService, private route: ActivatedRoute) {;}

  public ngOnInit() {
    this.route.params
      .subscribe((params: Params) => {
        debugger;
        console.log(params);
      });
    this.myService.getParams()
      .subscribe((params: Params) => {
        debugger;
        console.log('Return1:');
        console.log(params);
      }, (params: Params) => {
        debugger;
        console.log('Return2:');
        console.log(params);
      }, () => {
        debugger;
        console.log('Return3:');
    });
  }
};

My service looks like

import { Injectable }                     from '@angular/core';
import { Params, ActivatedRoute }         from '@angular/router';

import { Observable }                     from 'rxjs';

@Injectable()
export class MyService {
  constructor(private route: ActivatedRoute) {;}

  public getParams(): Observable<Params> {       
    this.route.params.subscribe((params: Params) => {
      debugger;
      console.log('Service1:');
      console.log(params);
    }, (params: Params) => {
      debugger;
      console.log('Service2:');
      console.log(params);
    }, () => {
      debugger;
      console.log('Service3:');
    });
    return this.route.params;
  }
};

When I debug I can see that params are filled in component and empty in service. That's the result

Component:
Object {param: "1"}
Service1:
Object {}
Return1:
Object {}

I'm using Angular 2.0.0. Why the difference in component and service? Is it possible to get params in a service?

EDIT: https://github.com/angular/angular/issues/11023

Disgorge answered 24/10, 2016 at 13:31 Comment(2)
did you try done section of subscribe method?Langille
I added onError and onComplete, but the functions aren't executed. I try to get params 3 times. Once directly in the component, once directly in the service and once in the component as a return from service. And only the first try works.Disgorge
P
4

We can pass ActivatedRoute to service from component. Then subscribe to route.params in service class

Paraffin answered 24/10, 2016 at 14:40 Comment(4)
Yes, that's the way I'm doing it right now. But it's a workaround. That way we have logic in the component and we have to implement this logic into every component I want to use this service in. Why isn't it possible in a service?Disgorge
Services are initialized at startup of application, then we are injecting into component wherever required. Components are initialized based on route changes, and the ActivatedRoute belongs to component. This is based on my understanding.Paraffin
@Thomas Sablik Thanks for sharing the linkParaffin
"I want to shift the logic from component to service"Bake
C
15

Acording to this you have to traverse down the route tree and get the data from the route at the bottom of the tree.

@Injectable()
export class MyService{

  constructor(private router:Router,private route:ActivatedRoute){   
   this.router.events
    .filter(event => event instanceof NavigationEnd)
     .subscribe((event) => {
         let r=this.route;
         while (r.firstChild) {
            r = r.firstChild
        }
         //we need to use first, or we will end up having
         //an increasing number of subscriptions after each route change.   
         r.params.first().subscribe(params=>{                
           // Now you can use the params to do whatever you want
         });             


    });            
  }
}
Carn answered 16/12, 2017 at 19:18 Comment(2)
This seems to work, but I had to remove first() from r.params.first() - is this an Angular version issue/is there some other way I can get the first param from the array?Defeasance
really great answer +1 ... however, this syntax is out of date ... see my answer belowLicentiate
P
4

We can pass ActivatedRoute to service from component. Then subscribe to route.params in service class

Paraffin answered 24/10, 2016 at 14:40 Comment(4)
Yes, that's the way I'm doing it right now. But it's a workaround. That way we have logic in the component and we have to implement this logic into every component I want to use this service in. Why isn't it possible in a service?Disgorge
Services are initialized at startup of application, then we are injecting into component wherever required. Components are initialized based on route changes, and the ActivatedRoute belongs to component. This is based on my understanding.Paraffin
@Thomas Sablik Thanks for sharing the linkParaffin
"I want to shift the logic from component to service"Bake
D
2

Something like this works for me in Angular 8:

export class TheService {

  params$: Observable<any>;

  constructor(private router: Router) {
    this.params$ = this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      map(event => this.getLeafRoute(this.router.routerState.root).snapshot.params)
    );
  }

  private getLeafRoute(route: ActivatedRoute): ActivatedRoute {
    if (route === null) return null; //or throw ?
    while (route.firstChild) route = route.firstChild;
    return route;
  }
}
Difference answered 9/1, 2020 at 22:36 Comment(0)
L
2

Credit to @juansb827 this is an updated continuation of his answer (which uses old RxJS syntax). Simply create a service as follows:

import { Injectable } from '@angular/core';
import { filter, first } from 'rxjs/operators';
import { ActivatedRoute, NavigationEnd, Params, Router, RouterEvent } from '@angular/router';
import { ReplaySubject } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class RouteParamsService {
  private routeParamsChangeSource = new ReplaySubject<Params>();
  routeParamsChange$ = this.routeParamsChangeSource.asObservable();

  constructor(private router: Router, private route: ActivatedRoute) {
    this.router.events
      .pipe(filter((event: RouterEvent) => event instanceof NavigationEnd))
      .subscribe(() => {
        let r = this.route;
        while (r.firstChild) r = r.firstChild;
        r.params.pipe(first()).subscribe((params: Params) => {
          this.routeParamsChangeSource.next(params);
        });
      });
  }
}

You can now hook into this service from anywhere in your app (including other services, components, interceptors, etc) as follows:

constructor(private routeParamsService: RouteParamsService) {
  this.routeParamsService.routeParamsChange$.subscribe((params: Params) => {
    console.log('params', params);
  });
}

And this will fire whenever the URL changes and emit the current params. Within a component you would place this code in ngOnInit instead of the constructor.

You may want to use a Subject instead of a ReplaySubject depending on your needs. A ReplaySubject will fire as soon as you subscribe with the last value emitted. A Subject will only fire on new emits after subscribing.

Licentiate answered 29/1, 2021 at 17:5 Comment(0)
A
2

This should help anyone wanting to get params recursively through all child routes:

import { Injectable } from '@angular/core';
import { Params, Router, ActivatedRoute, NavigationEnd } from '@angular/router';
import { BehaviorSubject } from 'rxjs';
import { filter, map, startWith } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class RouterParamsService {
  private routeParamsChangeSource = new BehaviorSubject<Params>({});
  change$ = this.routeParamsChangeSource.asObservable();

  constructor(private router: Router, private activatedRoute: ActivatedRoute) {
    const route$ = this.router.events.pipe(
      filter((event) => event instanceof NavigationEnd),
      map(() => this.activatedRoute)
    );

    const primaryRoute$ = route$.pipe(
      startWith(this.activatedRoute),
      map((route) => {
        let params = {};
        while (route.firstChild) {
          params = {
            ...params,
            ...route.snapshot.params
          };

          route = route.firstChild;
        }
        params = {
          ...params,
          ...route.snapshot.params
        };
        return { route, params };
      }),
      filter((data) => data.route.outlet === 'primary')
    );

    primaryRoute$.subscribe((data) => {
      this.routeParamsChangeSource.next(data.params);
    });
  }
}
Agency answered 11/6, 2021 at 1:51 Comment(0)
H
1

I like managing state through the URL, and built a simple state service that observes route navigation end events and exposes observable endpoints for each route parameter.

import { Injectable } from '@angular/core';
import {NavigationEnd, Router} from '@angular/router';
import {BehaviorSubject} from 'rxjs';
import { filter } from 'rxjs/operators';

@Injectable({
  providedIn: 'root'
})
export class DigStateService {
  public state = {};

  constructor(private router: Router) {
    this.router.events.pipe(
      filter(event => event instanceof NavigationEnd)
    ).subscribe(() => {
      let route = this.router.routerState.snapshot.root;
      do {
        const params = route.params;
        const keys = Object.keys(params);
        if (keys.length > 0) {
          keys.forEach(key => {
            const val = params[key];
            if (this.state[key]) {
              this.state[key].next(val);
            } else {
              this.state[key] = new BehaviorSubject(val);
            }
          });
        }
        route = route.firstChild;
      } while (route);
    });
  }

  param(key) {
    // if this key does not exist yet create it so its observable when it is set
    if (! this.state[key]) {
      this.state[key] = new BehaviorSubject(null);
    }
    return this.state[key];
  }
}

Then you can use this service to observe individual route params from anywhere in the tree:

stateService.param('project').subscribe(projectId => {
  console.log('project ' + projectId);
});
Hearts answered 13/1, 2019 at 22:33 Comment(0)
L
0

the problem is the

return this.route.params;

the route params are not ready at that moment -> observables -> asynchronicity

Langille answered 24/10, 2016 at 13:45 Comment(3)
this.route.params is an observable, so I return an observable and I subscribe to this observable. In addition as you can see it does't work in the service, too. Why?Disgorge
could you please put it into plnkr?Langille
I don't know how.Disgorge
O
0

I worked with @juansb827 answer and I got it to work when I got rid of the event filter and went directly for the traversal of that ActiveRoute. It worked for me. It's possible that, in my case, that event already happened by the time the service was being executed because my traversal was in a different method in my service.

Openfaced answered 27/6, 2020 at 0:12 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.