Angular 2 получает параметры маршрута в сервисе

#angular #angular2-routing #angular2-services

#angular #angular2-маршрутизация #angular2-services

Вопрос:

Я хочу перенести логику с компонента на сервис. Но я обнаружил, что не могу получить параметры маршрута в сервисе.

Мой компонент выглядит следующим образом

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

Мой сервис выглядит следующим образом

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

При отладке я вижу, что параметры заполнены в компоненте и пусты в сервисе. Вот результат

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

Я использую Angular 2.0.0. В чем разница в компоненте и сервисе? Возможно ли получить параметры в сервисе?

РЕДАКТИРОВАТЬ: https://github.com/angular/angular/issues/11023

Комментарии:

1. вы пробовали раздел done метода subscribe?

2. Я добавил onError и onComplete, но функции не выполняются. Я пытаюсь получить параметры 3 раза. Один раз непосредственно в компоненте, один раз непосредственно в сервисе и один раз в компоненте при возврате из сервиса. И работает только первая попытка.

Ответ №1:

В соответствии с этим вам нужно пройти вниз по дереву маршрутов и получить данные из маршрута в нижней части дерева.

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


    });            
  }
}
  

Комментарии:

1. Кажется, это работает, но мне пришлось удалить first() из r.params.first() — это проблема с версией Angular / есть ли какой-то другой способ получить первый параметр из массива?

2. действительно отличный ответ 1 … однако этот синтаксис устарел … смотрите мой ответ ниже

Ответ №2:

Мы можем передать ActivatedRoute в сервис из компонента. Затем подписывается на route.params в классе service

Комментарии:

1. Да, именно так я сейчас это и делаю. Но это обходной путь. Таким образом, у нас есть логика в компоненте, и мы должны внедрить эту логику в каждый компонент, в котором я хочу использовать этот сервис. Почему это невозможно в сервисе?

2. Сервисы инициализируются при запуске приложения, затем мы внедряем их в компонент везде, где требуется. Компоненты инициализируются на основе изменений маршрута, и активированный маршрут принадлежит компоненту. Это основано на моем понимании.

3. @Thomas Sablik Спасибо, что поделился ссылкой

4. » Я хочу перенести логику с компонента на сервис »

Ответ №3:

Что-то подобное работает у меня в 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;
  }
}
  

Ответ №4:

Благодаря @juansb827 это обновленное продолжение его ответа (в котором используется старый синтаксис RxJS). Просто создайте сервис следующим образом:

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

Теперь вы можете подключиться к этой службе из любого места вашего приложения (включая другие службы, компоненты, перехватчики и т.д.) Следующим образом:

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

И это будет срабатывать всякий раз, когда URL изменяется и выдает текущие параметры.
Внутри компонента вы бы поместили этот код в ngOnInit вместо конструктора.

Возможно, вы захотите использовать Subject вместо ReplaySubject в зависимости от ваших потребностей. ReplaySubject запустится, как только вы подпишетесь с последним переданным значением. Объект будет запускаться только при новых отправлениях после подписки.

Ответ №5:

Это должно помочь любому, кто хочет рекурсивно получать параметры по всем дочерним маршрутам:

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

Ответ №6:

Мне нравится управлять состоянием с помощью URL-адреса, и я создал простую службу состояния, которая отслеживает конечные события навигации по маршруту и предоставляет наблюдаемые конечные точки для каждого параметра маршрута.

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

Затем вы можете использовать этот сервис для просмотра отдельных параметров маршрута из любого места в дереве:

 stateService.param('project').subscribe(projectId => {
  console.log('project '   projectId);
});  

Ответ №7:

проблема в

 return this.route.params;
  

параметры маршрута на данный момент не готовы -> наблюдаемые -> асинхронность

Комментарии:

1. this.route.params является наблюдаемым, поэтому я возвращаю наблюдаемое и подписываюсь на это наблюдаемое. Кроме того, как вы можете видеть, это также не работает в сервисе. Почему?

2. не могли бы вы, пожалуйста, поместить это в plnkr?

3. Я не знаю как.

Ответ №8:

Я работал с ответом @juansb827 и заставил его работать, когда избавился от фильтра событий и перешел непосредственно к обходу этого активного маршрута. У меня это сработало. Возможно, что в моем случае это событие уже произошло ко времени выполнения сервиса, потому что мой обход был выполнен другим методом в моем сервисе.