【问题标题】:How can I re-call an Angular HttpClient observable when another observable changes?当另一个可观察对象发生变化时,如何重新调用 Angular HttpClient 可观察对象?
【发布时间】:2019-10-27 03:26:15
【问题描述】:

大局,我想要实现的是一组过滤器我的页眉,它控制我应用程序中多个分析页面的输入参数。我已将过滤器的功能封装到一个 Angular 服务中,该服务公开了一个可观察的对象,当过滤器发生更改时会发出。

我想要的是在 HttpClient 请求中使用这些过滤器值的服务订阅过滤器中的更改并在过滤器更改时重新运行其 HttpClient 请求(因此,例如,如果日期范围发生更改,我页面上的任何元素由该日期范围驱动的数据会自动更新)。

我的应用中的典型数据服务如下所示。看起来我想要做的事情应该足够简单,但我正在努力让我的头脑充分了解 RxJS 库,以便按照我的目标组合可观察对象。

export class DashboardDataService {

  constructor(
    private readonly http: HttpClient,
    private readonly globalFiltersService: GlobalFiltersService
  ) { }

  public getDashboard(): Observable<DashboardDto> {

    const filtersSubscription = globalFiltersService.filters$.subscribe(...);

    const observable = this.http.get<DashboardDto>(`${environment.apiBase}network/dashboard`, {
      params: this.globalFiltersService.getHttpParams()
    });

    // TODO: when filtersSubscription receives new data, make observable re-run it's HTTP request and emit a new response

    return observable; // Make this observable emit new data 
  }

}

我使用的是 Angular 8 和 RxJS 6,所以最好采用最现代的方式。

更新:工作实施

export class GlobalFiltersService {

  private readonly _httpParams$: BehaviorSubject<{ [param: string]: string | string[]; }>;
  private start: Moment;
  private end: Moment;

  constructor() {
    this._httpParams$ = new BehaviorSubject(this.getHttpParams());
  }

  public setDateFilter(start: Moment, end: Moment) {
    this.start = start;
    this.end = end;
    this._httpParams$.next(this.getHttpParams());
  }

  public get httpParams$() {
    return this._httpParams$.asObservable();
  }

  public getHttpParams() {
    return {
      start: this.start.toISOString(),
      end: this.end.toISOString()
    };
  }

}

export class DashboardDataService {

  private _dashboard$: Observable<DashboardDto>;

  constructor(
    private readonly http: HttpClient,
    private readonly globalFiltersService: GlobalFiltersService
  ) { }

  public getDashboard(): Observable<DashboardDto> {
    if (!this._dashboard$) {
      // Update the dashboard observable whenever global filters are changed
      this._dashboard$ = this.globalFiltersService.httpParams$.pipe(
        distinctUntilChanged(isEqual), // Lodash deep comparison. Only replay when filters actually change.
        switchMap(params => this.http.get<DashboardDto>(`${environment.apiBase}network/dashboard`, { params })),
        shareReplay(1),
        take(1)
      );
    }
    return this._dashboard$;
  }

}

export class DashboardResolver implements Resolve<DashboardDto> {

  constructor(private readonly dashboardDataService: DashboardDataService, private readonly router: Router) {}

  public resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<DashboardDto> {
    return this.dashboardDataService.getDashboard();
  }

}

【问题讨论】:

    标签: angular typescript rxjs angular-httpclient


    【解决方案1】:

    尝试以下方法:

    import {map, switchMap, shareReplay } from 'rxjs/operators';
    
    export class FooComponent {
      readonly dashboard$: Observable<DashboardDto>;
    
      ctor(...){
        this.dashboard$ = this.globalFiltersService.filters$.pipe(
          // map filter event to the result of invoking `GlobalFiltersService#getParams`
          map(_ => this.globalFiltersService.getHttpParams()),
          // maps the params to a new "inner observable" and flatten the result.
          // `switchMap` will cancel the "inner observable" whenever a new event is
          // emitted by the "source observable"
          switchMap(params => this.http.get<DashboardDto>(`${environment.apiBase}network/dashboard`, { params })),
          // avoid retrigering the HTTP request whenever a new subscriber is registered 
          // by sharing the last value of this stream
          shareReplay(1)
        );
      }
    }
    

    【讨论】:

    • 啊,非常感谢 - 这正是我所需要的。我从这里稍微调整了解决方案(稍后将在问题中发布),但这让我到达了我需要去的地方。我最终不需要第一个 map() 运算符,因为 filters$ 本身返回 getHttpParams() 的结果,我必须在共享重播后添加一个 take(1) 运算符才能让它发挥作用(我不完全理解为什么),但效果很好。
    • 更新:所以现在我想在其他两个 observable 中的任何一个发出新值时更新我的​​仪表板。不知道如何扩展此解决方案以应对该问题。任何建议将不胜感激。
    【解决方案2】:

    好问题!我必须同步 URL 参数、表单和查询结果。它把我从架构的兔子洞一路带到了状态管理。

    TL;DR 当有许多元素依赖于最新数据时,您需要对该数据具有高度可访问的状态。该解决方案更多的是关于架构,而不是使用哪种 RXJS 方法。

    这是我作为示例构建的服务stackblitz.com/edit/state-with-simple-service

    这是我的要求。 (直接适用于问题的)

    1. 分享所有选项的状态(从表单组件/URL接收)
    2. 与 URL 参数同步选项
    3. 使用选项同步所有表单
    4. 查询结果
    5. 分享结果

    这里是要点:

    export class SearchService {
        // 1. results object from the endpoint called with the current options
        private _currentResults = new BehaviorSubject<any[]>([]);
    
        // 2. current state of URL parameters and Form values
        private _currentOptions = new BehaviorSubject<Params>({});
    
        // 3. private options object to manipulate
        private _options: Params = {};
    

    然后使用 getter 访问这些:

    // Any component can subscribe to the current results from options query
    public get results(): Observable<any[]> {
        return this._currentResults.asObservable();
    }
    // Any component can subscribe to the current options
    public get options(): Observable<Params> {
        return this._currentOptions.asObservable();
    }
    

    随时使用next()更新私人主题

    this._currentOptions.next(this._options);
    

    现在您无需引入像 redux 这样的庞大框架即可进行状态管理。

    【讨论】:

    • 感谢 Ben - 这实际上与我在创建的 GlobalFiltersService 中已经实现的非常接近。我真的很想避免进入 redux - 这是一个大型应用程序中的一个相当小的要求,我得到的印象是 redux 最近倾向于在很多不需要它的 Angular 应用程序中使用(至少我是这样的)已阅读)。简单的 observables 提供了一些非常酷的状态管理功能——我只是发现这个库由于某种原因难以理解。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-02-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-23
    • 2018-04-13
    相关资源
    最近更新 更多