Is there a way to trigger a refresh of an Angular HttpClient observable based on changes in another observable?

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

}

Answer №1

Here is a code snippet for you to try:

import {pluck, switchMap, shareReplay } from 'rxjs/operators';

export class BarComponent {
  readonly data$: Observable<DataDto>;

  constructor(...){
    this.data$ = this.service.events$.pipe(
      // extract the specific property from each event
      pluck('data'),
      // switch to a new observable and flatten the result
      switchMap(data => this.http.get<DataDto>(`${environment.apiBase}data`, { data })),
      // share the last emitted value with all subscribers
      shareReplay(1)
    );
  }
}

Answer №2

Interesting query! I recently encountered a similar challenge where I had to synchronize URL parameters, form inputs, and query results. This led me on a deep dive into the realm of architecture, particularly focusing on state management.

In essence, when numerous elements rely heavily on up-to-date data, it becomes crucial to ensure easy access to that data through effective state management. The solution lies more in the architectural approach rather than simply choosing a particular RXJS method.

For reference, I created a sample service which you can explore here: stackblitz.com/edit/state-with-simple-service.

The requirements I faced directly align with the question:

  1. Sharing the state of all options (obtained from form components/URL)
  2. Synchronizing options with URL parameters
  3. Aligning all forms with options
  4. Querying for results
  5. Sharing the results

To summarize:

export class SearchService {
    // 1. Results object fetched from the endpoint based on 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 for manipulation
    private _options: Params = {};

You can then access these using getters:

// Any component can subscribe to the current results obtained 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();
}

Update the private Subjects whenever necessary using next()

this._currentOptions.next(this._options);

By implementing this approach, you can effectively manage state without the need for a complex framework like redux.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Retrieving data from Redis cache may not always yield the exact same data

I have been working on creating a small Express app that retrieves data from a PostgreSQL query and caches the result in a Redis database. Here is my approach: app.get('/query_tile/:z/:x/:y', async (req: Request, res: Response) => { const ...

Error causing expression change after Angular binding has been checked

Take a look at this demo: @Component({ selector: 'my-app', template: ` <div> <h1>{{ foo }}</h1> <bpp [(foo)]="foo"></bpp> </div> `, }) export class App { foo; } @Component({ ...

Displaying Typescript command line options during the build process in Visual Studio

As I delve into the world of VS 2015 Typescript projects, I find myself faced with a myriad of build options. Many times, the questions and answers on Stack Overflow mention command line options that I'm not completely familiar with, especially when i ...

Struggling to understand how to implement Schema Directives after transitioning to Typescript

After spending several days rewriting my GraphQL server to Typescript, I found myself struggling with defining Schema Directives. Despite extensive googling, I could not find any examples of using "more advanced" Apollo GraphQL with Typescript. Everything ...

What is the best way to loop through this particular value?

let myData = [{"id":"1","deleted":"0","data":[{"title":"Business Unit","value":"bus 1"},{"title":"Company ID","value":"comp 1" ...

Sending Angular forms to various endpoints

On a page, I have a form consisting of three different groups this.form = this.formBuilder.group({ profile: this.formBuilder.group({ name: [''], description: [''], }), members: this.formBuilder.array([ this.formBu ...

Tips for reducing the file size of your Angular 8 development build

Angular CLI version 8 does not automatically remove comments and minify js files in the development build. We have a specific need for optimized output files (such as vendor.js, main.js) when importing JitCompiler. To achieve this, I created a custom webpa ...

A TypeScript example showcasing a nested for-of loop within several other for loops

Is it possible to generate values from an Array of objects in the following way? const arr = [ { type: "color", values: [ { name: "Color", option: "Black", }, { name: "C ...

The information retrieved from the API is not appearing as expected within the Angular 9 ABP framework

I am facing an issue with populating data in my select control, which is located in the header child component. The data comes from an API, but for some reason, it is not displaying correctly. https://i.stack.imgur.com/6JMzn.png. ngOnInit() { thi ...

Can you guide me on how to specify the return type in NestJS for the Session User in a request?

async authenticated(@Req() request: Request) { const user = request.user return user } It is important for the 'user' variable to have the correct type globally. While working with express passport, I came across the following: decl ...

In TypeScript, a subclass can be assigned as a value of a superclass-type, but it cannot be directly used as a parameter in a function that specifically

class Parent { str = 'a'; } class ParentExtended extends Parent { num = 1; } class MyClass { static property?: Parent static method (p: Parent): void {} static func?: (pParam: Parent) => void } const pe: ParentExtended = { str: &ap ...

Is there a simpler method to retrieving data either from cache or through HTTP requests?

(When developing an Angular 2 app): What is the best approach for retrieving data? Should I use HTTP GET requests every time or is there a more efficient method? [STRATEGY] If the data is not already in memory and no GET request has been initiated, star ...

Problem with Zuul route not functioning correctly for SPA/Angular app

I have configured an API Gateway for a single API along with the UI. zuul: ignored-patterns: /health routes: fbresource: path: /fb/** url: http://localhost:8081/ angularresource: path: /** ...

Employing the ngFor directive, insert two elements onto a single row, then proceed to generate a fresh

For some time now, I've been attempting to loop through an array of objects using *ngFor and place two elements inside each row. The goal is to generate a new row after every two components have been added. Here's what I've attempted so far ...

Filtering based on the boolean value of a checkbox in Angular

I'm struggling to implement a boolean filter for my search results, separating users with financial debt from those without. I need some guidance on how to achieve this. Data Filter @Pipe({ name: 'filter' }) export class FilterPipe implem ...

Subscribing to an observable with a property that references itself

I am currently working on a class that stores time information and retrieves timestamps from the server. I need to format and display this date data. export class Product { timeCreated: number; // current method not functioning as expected ge ...

Is it possible for a component to have multiple templates that can be switched based on a parameter?

Scenario My task is to develop a component that fetches content from a database and displays it on the page. There needs to be two components working together to create a single page, following a specific component tree structure. DataList - receives ful ...

Error message: Unable to retrieve parameter value from Angular2 ActivatedRoute

I am attempting to showcase the value of the activated route parameter on the screen, but I am facing difficulties. In the initial component, my code looks like this: <ul *ngFor="let cate of categories;"> <li (click)="onviewChecklists(cate.id) ...

Securing Angular2 and ASP.NET Core with AntiForgery tokens

I am currently using an Angular2 app in conjunction with ASP.NET 5 (Core). The Http calls to the controller are functioning correctly. However, I now require setting up Cross Site Scripting protection. How can I create a new token for each Http request ...

Creating a table with merged (colspan or rowspan) cells in HTML

Looking for assistance in creating an HTML table with a specific structure. Any help is appreciated! Thank you! https://i.stack.imgur.com/GVfhs.png Edit : [[Added the headers to table]].We need to develop this table within an Angular 9 application using T ...