Как я могу повторно назвать Angular HttpClient наблюдаемым при изменении другой наблюдаемой? - PullRequest
3 голосов
/ 12 июня 2019

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

}

Ответы [ 2 ]

0 голосов
/ 12 июня 2019

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

0 голосов
/ 12 июня 2019

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

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