Currently have a scenario where a method within a shared service is used by multiple components. This method makes an HTTP call to an endpoint that will always have the same
Even though the solutions proposed by other before work, I find it annoying to have to manually create fields in each class for every different get/post/put/delete request.
My solution is basically based on two ideas: a HttpService that manages all http requests, and a PendingService that manages which requests actually go through.
The idea is to intercept not the request itself (I could have used an HttpInterceptor for that, but it would be too late because the different instances of the requests would have already been created) but the intention of making a request, before it's made.
So basically, all requests go through this PendingService, which holds a Set of pending requests. If a request (identified by it's url) is not in that set, it means this request is new and we have to call the HttpClient method (through a callback) and save it as a pending request in our set, with it's url as key, and the request observable as the value.
If later there's a request made to the same url, we check again in the set using its url, and if it's part of our pending set, it means... that is pending, so we return simply the observable we saved before.
Whenever a pending request is finished, we call a method to delete it from the set.
Here's an example assuming we're requesting... I don't know, chihuahas?
This would be our little ChihuahasService:
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { HttpService } from '_services/http.service';
@Injectable({
providedIn: 'root'
})
export class ChihuahuasService {
private chihuahuas: Chihuahua[];
constructor(private httpService: HttpService) {
}
public getChihuahuas(): Observable {
return this.httpService.get('https://api.dogs.com/chihuahuas');
}
public postChihuahua(chihuahua: Chihuahua): Observable {
return this.httpService.post('https://api.dogs.com/chihuahuas', chihuahua);
}
}
Something like this would be the HttpService:
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { share } from 'rxjs/internal/operators';
import { PendingService } from 'pending.service';
@Injectable({
providedIn: 'root'
})
export class HttpService {
constructor(private pendingService: PendingService,
private http: HttpClient) {
}
public get(url: string, options): Observable {
return this.pendingService.intercept(url, this.http.get(url, options).pipe(share()));
}
public post(url: string, body: any, options): Observable {
return this.pendingService.intercept(url, this.http.post(url, body, options)).pipe(share());
}
public put(url: string, body: any, options): Observable {
return this.pendingService.intercept(url, this.http.put(url, body, options)).pipe(share());
}
public delete(url: string, options): Observable {
return this.pendingService.intercept(url, this.http.delete(url, options)).pipe(share());
}
}
And finally, the PendingService
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/internal/operators';
@Injectable()
export class PendingService {
private pending = new Map>();
public intercept(url: string, request): Observable {
const pendingRequestObservable = this.pending.get(url);
return pendingRequestObservable ? pendingRequestObservable : this.sendRequest(url, request);
}
public sendRequest(url, request): Observable {
this.pending.set(url, request);
return request.pipe(tap(() => {
this.pending.delete(url);
}));
}
}
This way, even if 6 different components are calling the ChihuahasService.getChihuahuas(), only one request would actually be made, and our dogs API won't complain.
I'm sure it can be improved (and I welcome constructive feedback). Hope somebody finds this useful.