Search code examples
angularpromiserxjsrxjs-subscriptions

How can I ensure that multiple functions in different components only make a single API call using Angular, RxJS, and caching?


i'm using a cache service where i will store the api data in the file like this

import { Inject, Injectable } from "@angular/core";
import { Observable, firstValueFrom } from "rxjs";

@Injectable({
    providedIn: 'root'
})
export class ApiCacheService{
    api_data: any = null;

    constructor(
        private _api_service:ApiService
    ) { }
    
    public clearApiCache() {
        this.api_data=null
    }

    public async getApiData(body: any) {
        if (!this.api_data) {
            await firstValueFrom(this._api_service.getData(body)).then(res => {
                this.api_data=res
            }, err => {
                throw new Error("Error in api"+err["message"])
            })
         }
        return this.api_data
    }

}

my first component call will be

export class TestApiComponent {

    constructor(
        private _api_cache_service: ApiCacheService
    ) { }

    ngOnInit() {
        this.getData()
     }
    
    /**
     * function will get the data from the cache service
     */
    private async getData() {
        try {
            let body = {
                id:"1001"
            }
            let data = await this._api_cache_service.getApiData(body)
            // .... some other logic
        }catch{}
    }

}

and my second component also will call the same function parallel to the first one like this

export class SetApiComponent {

    constructor(
        private _api_cache_service: ApiCacheService
    ) { }

    ngOnInit() {
        this.setFunctionData()
     }
    
    /**
     * function will get the data from the cache service
     */
    private async setFunctionData() {
        try {
            let body = {
                id:"1001"
            }
            let data = await this._api_cache_service.getApiData(body)
            // .... some other logic
        }catch{}
    }

}

now when calling to the cache there is no data in the service and it will send request to the api, then before api response second service initiated the call and there is no data then it will also make a api call (for angular 13 and above).

here i need to make only one api call for multiple service calls and need to return same data to all services once the call was completed (need to call another api for different body in the request body).


Solution

  • The most simple solution, I think, would be that you set a property in the cache service that indicates if the getApiData method is running or not. In case it is not, it fetches the data through the API and it it is, then it uses an interval to check if the response has arrived or not:

    
    
    export class ApiCacheService {
      public api_data: any = null;
    
      private getApiDataRunning: boolean = false;
    
      constructor(
        private _api_service: ApiService,
      ) { }
    
      public clearApiCache() {
        this.api_data = null
      }
    
      public async getApiData(body: any) {
        if (this.api_data) {
          return this.api_data;
    
        } else if (this.getApiDataRunning) {
          await this.awaitApiData().then(api_data => {
            this.api_data = api_data;
          }, err => {
            throw new Error("Error in api"+err["message"]);
          });
          
          return this.api_data
    
        } else {
          this.getApiDataRunning = true;
    
          await this.fetchApiData().then(api_data => {
            this.api_data = api_data;
          }, err => {
            throw new Error("Error in api"+err["message"]);
          });
    
          this.getApiDataRunning = false;
          return this.api_data
        }
      }
    
      private async awaitApiData() {
        return await new Promise(resolve => {
          const interval = setInterval(() => {
            if (this.api_data) {
              resolve(this.api_data);
              clearInterval(interval);
            };
          }, 2000);
        });
      }
    
      private async fetchApiData() {
        return await firstValueFrom(this._api_service.getData(body)).then(res => {
          resolve(res);
        }, error => {
          reject(error);
        });
      }
    }