My main goal is to implement a set of filters in the page header that will control input parameters for various analytics pages within my app. I have created an Angular service that encapsulates the filter functionality and exposes an observable that emits changes to the filters.
The objective is to have services using these filter values in HttpClient requests subscribe to filter changes and automatically rerun their requests when the filters change. For example, if a date range changes, any elements on the page dependent on that range should update seamlessly.
A typical data service structure in my app appears as follows. While this task seems straightforward, I am finding it challenging to work with RxJS to combine observables in the desired manner.
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, trigger observable to re-run its HTTP request and emit updated response
return observable; // Have this observable emit new data
}
}
I am working with Angular 8 and RxJS 6, so leveraging the most up-to-date methods is preferred.
UPDATE: Successful implementation
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), // Using Lodash deep comparison. Replaying only 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();
}
}