Ways to initiate a fresh API request while utilizing httpClient and shareReplay

I have implemented a configuration to share the replay of my httpClient request among multiple components. Here is the setup:

apicaller.service.ts

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
// import rxjs map
import { map, shareReplay } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class ApicallerService {
  peopleindex: number = 1;
  constructor(private _http: HttpClient) {}

  private readonly request = this._http
    .get('https://swapi.dev/api/people/' + this.peopleindex)
    .pipe(shareReplay());

  getData(peopleindex: number = 1) {
    this.peopleindex = peopleindex;
    return this.request;
  }
}

Component1

This component should request the API to retrieve details for id 2

import { Component, OnInit } from '@angular/core';
import { ApicallerService } from '../apicaller.service';

@Component({
  selector: 'app-component1',
  templateUrl: './component1.component.html',
  styleUrls: ['./component1.component.css'],
})
export class Component1Component implements OnInit {
  apiresponse: any;
  editRes: any;
  constructor(private _apicaller: ApicallerService) {}

  ngOnInit(): void {
    this._apicaller.getData(2).subscribe((data: any[]) => { // <-- Notice 2 in the argument
      console.log(data);
      this.apiresponse = data;
    });
  }
}

Component2

This component requests data for the default ID

import { Component, OnInit } from '@angular/core';
import { ApicallerService } from '../apicaller.service';

@Component({
  selector: 'app-component2',
  templateUrl: './component2.component.html',
  styleUrls: ['./component2.component.css']
})
export class Component2Component implements OnInit {

  apiresponse: any = {};
  constructor(
    private _apicaller: ApicallerService
  ) { }

  ngOnInit(): void {
    // get data from api using service
    this._apicaller.getData().subscribe(
      (data) => {
        this.apiresponse = data;
      }
    );
  }

}

However, there seems to be an issue where component1 is not making a new request with peopleindex = 2.

To see and reproduce the problem, you can check out my StackBlitz setup here: https://stackblitz.com/edit/angular-ivy-gdxyy6?file=src%2Fapp%2Fcomponent1%2Fcomponent1.component.ts

Answer №1

One recommendation is to establish an API store for scenarios like this.

  • Initially, the API will be stored with a specific ID and return the initial call.

  • Subsequently, the stored API will be utilized on subsequent calls.

export class ApicallerService {
  apiStore = new Map();
  constructor(private _http: HttpClient) {}

  private getApiRequest(id) {
    return this._http.get('https://swapi.dev/api/people/' + id).pipe(shareReplay());
  }

  getData(peopleindex: number = 1) {
    if (!this.apiStore.has(peopleindex)) {
      this.apiStore.set(peopleindex, this.getApiRequest(peopleindex));
    }
    return this.apiStore.get(peopleindex);
  }
}

Answer №2

Utilizing ShareReplay can be advantageous when you require consistent metadata (masterdata) that remains the same across various components, allowing for cached or shared data.

To ensure the expression is executed with updated values, it's essential to include a getter for the request instead of directly accessing the field.

private get request() { 
    return this._http.get('https://swapi.dev/api/people/' + this.peopleindex);
}

The common practice for making httpService calls involves creating a dedicated service to handle REST endpoints by wrapping around the httpClient.

@Injectable({
  providedIn: 'root',
})
export class ApiService {
  constructor(private _http: HttpClient) {}

  get(url: string) {
    return this._http
    .get(url);
  }

  post(url: string, data) {
    return this._http
    .post(url, data);
  }

}

Subsequently, establish a feature service that relies on the apiService.

@Injectable({
  providedIn: 'root',
})
export class PeopleService {
  constructor(private _api: ApiService) {}

  getData(peopleindex: number = 1) {
    console.log(peopleindex);
    return this._api
    .get('https://swapi.dev/api/people/' + peopleindex);
  }
}

Invoke these feature services within your respective feature components.

export class Component1Component implements OnInit {
  apiresponse: any;
  editRes: any;

  constructor(private _peopleService: PeopleService) {}

  ngOnInit(): void {
    this._peopleService.getData(2).subscribe((data: any[]) => {
      this.apiresponse = data;
    });
  }
}

export class Component2Component implements OnInit {
      apiresponse: any = {};
      constructor(
        private _peopleService: PeopleService
      ) { }

      ngOnInit(): void {
        // Obtain data from API using the service
        this._peopleService.getData().subscribe(
          (data) => {
            console.log(data);
            this.apiresponse = data;
          }
        );        
      }
    }

For further reference, you can access the StackBlitz link here.

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

How to Access Static Method from Non-Static Method in TypeScript

Within my hierarchy, there is some static content present in all levels (for example, the _image). It would be ideal to access the corresponding _image without repeating code: Here's what I envision: class Actor { static _image; // Needs to be s ...

The type undefined cannot be assigned to the type even with a null check

When looking at my code, I encounter an error stating Argument of type 'Definition | undefined' is not assignable to parameter of type 'Definition'. Even though I am checking if the object value is not undefined with if (defs[type] != u ...

Is there a way to set an antd checkbox as checked even when its value is falsy within an antd formItem?

I'm currently looking to "invert" the behavior of the antd checkbox component. I am seeking to have the checkbox unchecked when the value/initialValue of the antD formItem is false. Below is my existing code: <FormItem label="Include skills list ...

The Subject<T> generic type needs to be provided with 1 type argument

Currently, I am setting up Angular Datatables the Angular Way using Angular 6 and encountering an error that I cannot find in any of the documentation. (TS) Generic type 'Subject' requires 1 type argument(s) When hovering over "Subject" in the ...

Opening an external link from the Side Menu in Ionic4: A step-by-step guide

In my Ionic project, I have implemented a side menu in the app.html file that is accessible throughout the entire application. This menu contains items with links that need to be opened externally. However, when trying to open them using InAppBrowser, an e ...

Encountered an error stating "Cannot find module node:fs" while using eslint-typescript-import, eslint-import-resolver-typescript,

Challenge My attempt to configure path alias in my TypeScript project was met with failure. Two errors arose during the execution of npm start: Module not found: Error: Can't resolve '~/App' in 'D:\work\workbench\templa ...

Why does Material-UI's "withStyles()" not function properly with a specified constructor in Typescript?

Update: Please note that there was an issue with the constructor generated by IntelliJ IDEA, but it has been fixed. You can find more details here: I'm exploring the use of Material-UI in my application, and I've encountered some challenges wit ...

Adding properties to a class object in Javascript that are integral to the class

Recently, I've been contemplating the feasibility of achieving a certain task in JavaScript. Given my limited experience in the realm of JavaScript, I appreciate your patience as I navigate through this. To illustrate what I am aiming for, here' ...

Exploring generic types using recursive inference

The scenario: export type SchemaOne<T> = | Entity<T> | SchemaObjectOne<T>; export interface SchemaObjectOne<T> { [key: string]: SchemaOne<T>; } export type SchemaOf<T> = T extends SchemaOne<infer R> ? R : nev ...

Is there a way to efficiently compare multiple arrays in Typescript and Angular?

I am faced with a scenario where I have 4 separate arrays and need to identify if any item appears in more than two of the arrays. If this is the case, I must delete the duplicate items from all arrays except one based on a specific property. let arrayA = ...

What is the best way to link labels with input fields located separately in Angular?

Imagine a scenario where labels and form fields are being created in a *ngFor loop, as shown below: app.component.ts export class AppComponent { items = ['aaa', 'bbbbbb', 'ccccccccc'] } app.component.html <div class ...

Inline styling for a Cypress test on an Angular component within an SVG markup

Testing out this SVG for a specific purpose, without needing to explain why. Running some tests on it! <svg class="custom-background" width="1864" height="441" style="background: linear-gradient(to right, rgb(255, 255, ...

Encountering an issue with Angular 2.0.1 Router where it throws an EmptyError due to

I am struggling to set up Angular 2 routing properly. I am currently using Angular 2 with Webpack. While looking at the Angular 2 webpack starter, I noticed they had webpack handling their html and links generation, but I was hoping to avoid webpacking my ...

Unlock the full potential of working with TaskEither by utilizing its powerful functionality in wrapping an Option with

After exploring various examples of using TaskEither for tasks like making HTTP requests or reading files, I am now attempting to simulate the process of retrieving an item from a database by its ID. The possible outcomes of this operation could be: The i ...

Guide on extracting FormData from a POST request in Node.js using Multer

I have a specific challenge where I need to store formData on a mongodb schema, using nodejs and express for the backend, along with multer as middleware. This particular formData consists of both a string and a blob. My main issue is capturing and saving ...

Is there a way to access the value or key of a JSON property in an Angular template for rendering purposes?

Having trouble displaying the JSON values of certain properties on screen. Utilizing Angular Material table to showcase my JSON response. The code snippet below is responsible for rendering the JSON data: <mat-card-content class="dashboard-card-cont ...

SvgIcon is not a recognized element within the JSX syntax

Encountering a frustrating TypeScript error in an Electron React App, using MUI and MUI Icons. Although it's not halting the build process, I'm determined to resolve it as it's causing issues with defining props for icons. In a previous pro ...

Using path aliases in a Typescript project with Typescript + Jest is not a viable option

Note I am able to use aliases in my TypeScript file. Unfortunately, I cannot use aliases in my test files (*.test.ts). My Configuration 1. Jest.config.ts import type { Config } from '@jest/types'; const config: Config.InitialOptions = { ve ...

A fresh perspective on incorporating setInterval with external scripts in Angular 7

Incorporating the header and footer of my application from external JavaScript files is essential. The next step involves converting it to HTML format and appending it to the head of an HTML file. private executeScript() { const dynamicScripts = [this.app ...

When a ListView item is clicked, a label will display text with text wrapping specific to the selected item in the list

Within the listview items, there is a label that should expand when clicked. For example, initially it only shows one line of text. Upon clicking on the label, it should expand to show 10 lines of text. Current Issue: At present, when I click on the firs ...