Как я могу повторно вызвать наблюдаемый Angular HttpClient при изменении другого наблюдаемого?

Общая картина, которую я пытаюсь достичь, - это набор фильтров моего заголовка страницы, которые контролируют входные параметры для нескольких страниц аналитики в моем приложении. Я инкапсулировал функциональность фильтров в службу 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();
  }

}
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Angular и React для вашего проекта веб-разработки?
Angular и React для вашего проекта веб-разработки?
Когда дело доходит до веб-разработки, выбор правильного front-end фреймворка имеет решающее значение. Angular и React - два самых популярных...
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Эпизод 23/17: Twitter Space о будущем Angular, Tiny Conf
Мы провели Twitter Space, обсудив несколько проблем, связанных с последними дополнениями в Angular. Также прошла Angular Tiny Conf с 25 докладами.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
Мое недавнее углубление в Angular
Мое недавнее углубление в Angular
Недавно я провел некоторое время, изучая фреймворк Angular, и я хотел поделиться своим опытом со всеми вами. Как человек, который любит глубоко...
Освоение Observables и Subjects в Rxjs:
Освоение Observables и Subjects в Rxjs:
Давайте начнем с основ и постепенно перейдем к более продвинутым концепциям в RxJS в Angular
4
0
1 615
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Попробуйте следующее:

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(), потому что filter$ сам по себе возвращает результат getHttpParams(), и мне пришлось добавить оператор take(1) после общего повтора, чтобы заставить его играть в мяч (я не полностью понятно почему) но работает отлично.

wwarby 12.06.2019 21:32

Обновление: теперь я хочу обновить свою панель инструментов, когда любой из двух других наблюдаемых выдает новые значения. Не понял, как расширить это решение, чтобы справиться с этим. Мы ценим любые предложения.

wwarby 18.06.2019 12:38

Хороший вопрос! Мне пришлось синхронизировать параметры 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 = {};

Затем получите доступ к ним с помощью геттеров:

// 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.

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

wwarby 12.06.2019 21:35

Другие вопросы по теме