提问者:小点点

服务如何订阅当前路由的ParamMap?


我想创建一个订阅Angular参数Map更改的服务。不幸的是,这似乎不起作用。

export class LocationResourcesService {

  constructor(private activatedRoute: ActivatedRoute) {

    this.activatedRoute.paramMap.pipe(
      map((params: ParamMap) => params.get('account-id')),
    ).subscribe((newValue) => console.log(newValue))
  }
  // ----------
}

上面的订阅只发出一个值-当Angular第一次加载到页面时。该值是null。当它被放置在页面中活动的组件的构造函数中时,相同的订阅会这样做。大概是因为路由已经加载并且ActivatedRoute已经设置。

我会假设ActivatedRoute是一个单例服务,因此我可以订阅它的更改。显然情况并非如此,那么该服务如何订阅tivatedRoute. ParamMap的值?


共3个答案

匿名用户

不幸的是,这个问题没有简单的答案。我不打算重现整个讨论,但解决方案可以在这里看到:https://github.com/angular/angular/issues/11023#issuecomment-399667101.

我把它复制过来,以便更容易感受到它。正如我所说,这并不简单:

import { Injectable } from '@angular/core';
import { ActivatedRoute, Router, NavigationEnd, Params } from '@angular/router';
import { filter, switchMap } from 'rxjs/operators'
import { of, Observable } from 'rxjs'

@Injectable()
export class MiscService {

  params$: Observable<Params>

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

    // Create an observable representing the params
    this.params$ = this.router.events.pipe(
      filter(event => event instanceof NavigationEnd),
      switchMap(() => {
        const params = this.route.firstChild?.params
        // return params or an empty observable 
        return params || of()
      })
    )
    
    // subscribe to the params$ observable - can be done for 
    // multiple observers
    this.params$.subscribe((params) => {
      console.log('params', params)
    })
  }

}

匿名用户

这是我所做的(使用ReplaySub的纯RxJS实现):

将存储参数(例如parthing)的公共服务(作为可观察对象):

@Injectable({
  providedIn: "root",
})
export class MyService {
  // use ReplaySubject to store/observe the paramThing
  paramThing$ = new ReplaySubject<string | null>(1);

  constructor() {}
}

然后在ActivatedRoute给出正确值的组件中:

@Component({
  selector: "app-xxx",
  templateUrl: "./xxx.component.html",
  styleUrls: ["./xxx.component.scss"],
})
export class XxxComponent implements OnInit, OnDestroy {
  private subs: Subscription[] = [];
  constructor(
    private readonly actRoute: ActivatedRoute,
    private readonly myService: DashService
  ) {}

  ngOnInit(): void {
    const subscription = this.actRoute.paramMap
      .pipe(map((parameters) => parameters.get("paramThing")))
      .subscribe((paramThingValue) => {
        this.myService.paramThing$.next(paramThingValue); // <=== this does the trick
      });
    this.subs.push(subscription);
  }

  ngOnDestroy(): void {
    for (const item of this.subs) item.unsubscribe();
    this.subs = [];
    // if you need, you can unset the value when component goes out of scope
    this.myService.paramThing$.next(null);
  }
}

然后在任何消费组件/服务中,您都可以使用MyService来获取parthing的值:

我需要parthing的示例服务。

import { lastValueFrom, take, switchMap } from "rxjs";

@Component({
  selector: "app-ccc",
  templateUrl: "./ccc.component.html",
  styleUrls: ["./ccc.component.scss"],
})
export class CccComponent implements OnInit {
  constructor(
    private readonly myService: MyService
  ) {}

  async ngOnInit() {
    const paramThing = await lastValueFrom(this.myService.paramThing$.pipe(take(1)));
    // paramThing:null | string
    
    // or if you want an observable chain for paramThing
    this.myService.paramThing$.pipe(
      switchMap(paramThing => {
        return anotherPromiseOrObservableChain;
      }),
      // ...
    ).subscribe(...)
  }
}

匿名用户

Peter Nixey的解决方案对我不起作用,除非我将ActivatedRoute服务注入到我想要订阅更改的组件中。虽然Wajahath的解决方案有效,但它很hacky,因为您需要在使用该值之前从组件注入到服务。

我提出以下解决方案。它包括获取当前路由参数、所有路由参数更改的可观察值和特定路由参数更改的可观察值(按参数名称)。

@Injectable({
    providedIn: "root"
})
export class ParamsService {

    /**
     * The route params.
     */
    readonly routeParams$: Observable<Params> = this.router.events.pipe(
        filter(event => event instanceof NavigationEnd),
    ).pipe(
        map(_ => this.getCurrentRouteParams()),
        distinctUntilChanged(),
    );

    /**
     * Gets the observable of a specific route param.
     */
    routeParamByName(paramName: string, defaultValue: string): Observable<T> {
        return this.routeParams$.pipe(
            map(params => params[paramName] ?? defaultValue),        
            distinctUntilChanged(),
        );
    }

    /**
     * Gets the current params in the activated route.
     * @see https://stackoverflow.com/questions/39977962/angular-2-0-2-activatedroute-is-empty-in-a-service/74915199#74915199
     */
    getCurrentRouteParams(): Params {

        let params: any = {};
        let stack: ActivatedRouteSnapshot[] = [this.router.routerState.snapshot.root];

        while (stack.length > 0) {
            const route = stack.pop()!;
            params = {...params, ...route.params};
            stack.push(...route.children);
        }

        return params;

    }

}