'Prevent multiple identical http calls from Angular service

I've a service to get the data from an API in my angular app. The service is used in different components - and the same http request is send multiple times. To prevent those multiple calls of the same data, I used the shareReplay() operator.

@Injectable({
    providedIn: "root",
})

export class CurrentValuesService {
   
    constructor(private http: HttpClient, private adapter: SingleMeasurePointAdapter) { }

    getLastEntry(): Observable<SingleMeasurePoint> {        

       ...
  
       let lastEntry = this.http.get(url)
          .pipe(            
             map((receivedData: any) => {
                console.log(receivedData);
                const data = ...              
                return data;
             }),
             shareReplay()            
          );
       return lastEntry;
    }
}

Calling the getLastEntry() function in the components:

ngOnInit(): void {
   this.currentValuesService.getLastEntry().subscribe((data: SingleMeasurePoint) => {
      ...       
   });
}

But nothing changes - the same request is send for every subscribing component again - and not only once.

Am I totally wrong? Or are there other ways to prevent multiple calls?



Solution 1:[1]

You could use SharedDataService to save Your request response and use it in other pages:

SharedDataService has as variables and methods:

_lastEntry // declare your var

get lastEntry(){
return this._lastEntry;

}
set lastEntry(value){
this._lastEntry = value;
}

Then on your component check if it has a value:

constructor(public sharedDataService : SharedDataService){}

ngOnInit(): void {

let lastEntry = this.sharedDataService.lastEntry
if(!lastEntry){
 this.currentValuesService.getLastEntry().subscribe((data: SingleMeasurePoint) => {
this.lastEntry = lastEntry

      ...       
   });
}

else{
this.lastEntry = lastEntry

}


  
}

Solution 2:[2]

Maybe you have a look at the proposal here: Caching

Look at the second code snippet: A member variables is used for storing the returned observable from http client.

Solution 3:[3]

I suggest to adopt a different, reactive, approach.

What I mean is the following.

A service should expose 2 types of APIs:

  1. Normal methods that clients (e.g. Components) can invoke to execute commands
  2. Observables that clients (e.g. Components) can subscribe to if they want to be notified of specific events

If you adopt such an approach, the code of your service would look like this

@Injectable({
    providedIn: "root",
})

export class CurrentValuesService {
    // you define a private ReplaySubject that will be used to notify the results of
    // the command to any client which subscribes to its corresponding Observable
    // since we use a ReplaySubject, the last value notified by it
    // will be replayed when any client subscribe to it
    private _entrySubject = new ReplaySubject<any>(1);
    // this is the Observable exposed as API
    public entry = this._entrySubject.asObservable();
   
    constructor(private http: HttpClient, private adapter: SingleMeasurePointAdapter) { }

    // getLastEntry is the API method that executes a command and does not return anything
    public getLastEntry() {        

       ...
  
       this.http.get(url)
          .pipe(            
             map((receivedData: any) => {
                console.log(receivedData);
                const data = ...              
                return data;
             }),
             // with the following tap operator you broadcast notification, 
             // errors and completion using the private subject so that
             // any client which has subscribed the corresponding Observable
             // (i.e. which has subscribed to this.entry$) will receive
             // the same notification, errors or completion 
             tap(this._entrySubject)            
          )
          .subscribe();
    }
}

Now that you have such service, then a Component which is interested in the lastEntry, can use it like this

ngOnInit(): void {
   this.currentValuesService.entry$.subscribe((data: SingleMeasurePoint) => {
      ...       
   });
}

Now we have to decide who triggers the execution of the http service, in other words who calls the method getLastEntry of the service.

One option is to identify the top Component and call the getLastEntry method from there during the initialization phase (e.g. within ngOnInit).

The other option is to call the method directly from the constructor of the service.

Either way, it is important that the getLastEntry is called only once, unless for other reasons you need to call it again during the lifetime of the application.

Last point is related to the fact that we use a ReplaySubject. This is to ensure that Components that subscribe to the Observable after the http call has completed get anyway the last result returned by the call.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Zrelli Majdi
Solution 2 daflodedeing
Solution 3