Thomas Sablik
Thomas Sablik

Reputation: 16453

Angular 2 get routeParams in a service

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

Upvotes: 14

Views: 15600

Answers (8)

Tonksi
Tonksi

Reputation: 21

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);
    });
  }
}

Upvotes: 2

danday74
danday74

Reputation: 56966

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.

Upvotes: 2

Nick2324
Nick2324

Reputation: 45

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.

Upvotes: 0

Zar Shardan
Zar Shardan

Reputation: 5921

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;
  }
}

Upvotes: 2

ForrestLyman
ForrestLyman

Reputation: 1652

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);
});

Upvotes: 1

juansb827
juansb827

Reputation: 151

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
         });             


    });            
  }
}

Upvotes: 15

Eswar
Eswar

Reputation: 4983

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

Upvotes: 4

user3025289
user3025289

Reputation:

the problem is the

return this.route.params;

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

Upvotes: 0

Related Questions