Robbie Mills
Robbie Mills

Reputation: 2945

Sharing the results of an API call throughout an Angular application

How do I store a local variable or cache it so that the results of an API call can be shared throughout the application?

I have the following Angular 6 module which basically creates a mentions directive to be able to do @mentions popular in many apps:

@NgModule({
    imports: [
        CommonModule
    ],
    exports: [
        MentionDirective,
        MentionListComponent
    ],
    entryComponents: [
        MentionListComponent
    ],
    declarations: [
        MentionDirective,
        MentionListComponent
    ]
})
export class MentionModule {
    static forRoot(): ModuleWithProviders {
        return {
            ngModule: MentionModule
        };
    }
}

This is the actual directive:

export class MentionDirective implements OnInit, OnChanges {
items: any[];

constructor(
  public autocompletePhrasesService: AutocompletePhrasesService
) { }

  ngOnInit() {
    this.autocompletePhrasesService.getAll()
      .pipe(first())
      .subscribe(
        result => {
          this.items = result;
        },
        () => { });

The directive calls a function in my core module which retrieves the data:

export class AutocompletePhrasesService {

    public autoCompletePhrases: string[];

    constructor(private http: HttpClient) { }

    getAll(): Observable<string[]> {
        return this.http.get<string[]>(this.baseUrl + '/getall');
    }

My problem is that this directive may have 20-30 instances on a single page and the API gets called for every instance of the directive. How can I change my code so that the API gets called just once per application instance?

The data does not change often from the API.

I have tried storing the results of the service in the public autoCompletePhrases variable, and only calling that if it is empty, but that hasn't worked

Upvotes: 2

Views: 815

Answers (2)

Dale K
Dale K

Reputation: 27289

Here is some AngularJS/TypeScript code to demonstrate the logic of how to handle this. Apologies but I don't have time to convert it to Angular. I do believe there are also packages out there which handle caching in a more complete fashion but this is the manual way to do it :)

private printerList: SelectItem[] = [];
private printerPromises: IDeferred<SelectItem[]>[] = [];
private printersLoading: boolean = false;
private printersLoaded: boolean = false;

public getPDFPrinterList(): ng.IPromise<SelectItem[]> {
  let defer: IDeferred<SelectItem[]> = this.$q.defer<SelectItem[]>();

  this.printerPromises.push(defer);

  if (this.printersLoading) {
      return defer.promise;
  }

  if (this.printersLoaded) {
      for (let i = 0; i < this.printerPromises.length; i++) {
          this.printerPromises[i].resolve(this.printerList);
      }
      this.printerPromises = [];
  } else {
      this.printersLoading = true;
      this.$http.get<SelectItem[]>(this.printerListUrl, { cache: false }).then((response: ng.IHttpResponse<SelectItem[]>) => {
          if (response.data) {
              this.printerList = response.data;
              for (let i = 0; i < this.printerPromises.length; i++) {
                  this.printerPromises[i].resolve(this.printerList);
              }
              this.printerPromises = [];
              this.printersLoaded = true;
          } else {
              console.log('Server Error Obtaining Printer List');
              for (let i = 0; i < this.printerPromises.length; i++) {
                  this.printerPromises[i].reject('Server Error');
              }
              this.printerPromises = [];
          }
          this.printersLoading = false;
      }, (response: ng.IHttpResponse<any>) => {
          if (response.data) {
             console.log(response.data.Message ? response.data.Message : response.data);
          }
          for (let i = 0; i < this.printerPromises.length; i++) {
              this.printerPromises[i].reject('Server Error');
          }
          this.printerPromises = [];
      });
  }

  return defer.promise;
}

Upvotes: 1

Eliseo
Eliseo

Reputation: 57981

You want some like "cache". Its easy in service make some like

data:any;
getData()
{
    if (!this.data)
      return this.httpClient.get(....).pipe(
        tap(result=>{
            this.data=result
        }))
    return of(this.data)
}

When you subscribe to "getData" can happen two things: this.data has value, then return an observable with the data. Or this data has no value, and return a get, and store the result in data -tap is executed after subscribe-. So you ALLWAYS subscribe to "getData", but the service only call to API if has not data (NOTE: can happens that there're more that one call at time and the call to API more than one time)

Upvotes: 1

Related Questions