4

I have a service call, whose response is cached inside my Angular service like this:

public cacheMyServiceResponse(): Observable<any> {
  return this.appConfig.getEndpoint('myService')
    .pipe(
      switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
      tap((body: any) => {
        if (body) { //  this endpoint can also return a 204 (No Content), where body is null
          this.cache = body.myData;
        }
      }),
      take(1),
      catchError(error => {
        this.errorService.trackError(error.status);
        return of(true);
      })
    );
}

So the response of my http.get call will be cached here, in a global variable called "cache".

The problem is, this call may really response very late, so we wanted to call this endpoint as soon as our page loads (while initialization). But the actual response, or whether our call is finished (either success or error), we need this info only then when user clicks a button.. Of course at that moment of button click, the response may not be there yet, and in this case i would like to wait for it. (so i need more than a simple boolean flag)

So i want to initialize this call in a ngOnInit like this:

ngOnInit() {
    this.myService.cacheMyServiceResponse().subscribe();
}

But somewhere else i need to know if it is already finished the call, without firing my http call twice.

onClick() {
    this.myService.cacheMyServiceResponse().subscribe(() => {
       // call is finished..
    });
}

At the moment the service will be called twice. How can i do this?

PS: i dont have an error handling on purpose, i just have to know if the service call finished at all.

1
  • You can user Resolvers here for your scenario . It will call your method when you hit to your route. Example: @Injectable() export class APIResolver implements Resolve<any> { constructor(private apiService: APIService) {} resolve(route: ActivatedRouteSnapshot) { return this.apiService.getItems(route.params.date); } } { path: 'items/:date', component: ItemsComponent, resolve: { items: APIResolver } } Commented Aug 6, 2019 at 11:46

5 Answers 5

7

I suggest to use ReplaySubject() and subscribe to the ReplaySubject() onClick instead, it will wait for your service to emit data while it still can be subscribed to, also if it did not be subscribed before the data from service emit, you wont miss the data:

yourWaitingData = new ReplaySubject();
subscription;

ngOnInit() {
    this.myService.cacheMyServiceResponse().subscribe(res => {
        //yourWaitingData only emit when res is return from API call
        this.yourWaitingData.next(res)
    });
}

Then subscribe to it:

onClick() {
    if(this.subscription){
       this.subscription.unsubscribe()
    }
    this.subscription = this.yourWaitingData.subscribe((x) => {
       // subscribed and will wait for data to be emited from service
       console.log(x)
    });
}
Sign up to request clarification or add additional context in comments.

2 Comments

Maybe a BehaviorSubject would be better, otherwise you may miss the response
@Random thank you for pointing out, updated my solution using ReplaySubject
1

You can user Resolvers here for your scenario . It will call your method when you hit to your route.

Example:

@Injectable()
export class ExampleResolver implements Resolve<any> {
  constructor(private apiService: APIService) {}

  resolve(route: ActivatedRouteSnapshot) {
    return this.apiService.getItems(route.params.date);
  }
}

Your Route :

{
  path: 'routeName',
  component: YourComponent,
  resolve: { items: ExampleResolver }
}

1 Comment

It will get your response before your ngOnInit() . Also you can store your response in your service cache.
1

Use shareReplay to create a simple cache that fires the http request once and provides it's return value to all subsequent subscriptions from cache.

Service

private cache$: Observable<any>;

getData(): Observable<any> {
  if (!this.cache$) {
    this.cache$ = this.requestData().pipe(shareReplay(1));
  }
  return this.cache$;
}

private requestData(): Observable<any> {
  return this.appConfig.getEndpoint('myService')
    .pipe(
      switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
      catchError(error => {
        this.errorService.trackError(error.status);
        return of(true);
      })
    );
}

Component

You can subscribe to this.myService.getData() multiple times without firing multiple http calls. Only the first subscription fires a call.

ngOnInit() {
  this.myService.getData().subscribe();
}

onClick() {
  this.myService.getData().subscribe(data => console.log('data from click'));
}

https://stackblitz.com/edit/angular-ysafwb

1 Comment

Great answer, I think this is the much cleaner approach then what the accepted one indicates. Less boilerplate code and no additional observable/replay subject required.
0

Use a boolean variable to check the state of the actual response, or whether our call is finished (either success or error); This boolean can then be checked then when user clicks a button... following code to explain what i mean...

  callMade:boolean = false;
  callFinished:boolean = false;

  ngOnInit(){
    this.callMade = true;
    this.myService.cacheMyServiceResponse().subscribe(
          dataa => { /* get and process data */}
          ,() => { /*this is the finally block */
          this.callFinished = true;
          }
    );
  }

  someOtherFunction(){
    if (this.callMade == true && this.callFinished == false){
      /* call the service again */
    }
  }

3 Comments

first of all, in my example, the button can also be called as soon as the page loads, and there i would like to wait for let's say max. 10 seconds.. so i cannot actually just check a variable.. secondly i actually asked the question with the hope that this can be achieved with pure rxjs ..
@akcasoy you didn't ask that last part especially clearly then.
sorry. but as soon as you have a service call, you always have to deal with those cases where the reponse is not there yet.. in my question i used ".subscribe(()" in onClick callback function. i thought it makes it clear that i would like to wait for the response
0

Why don't you save the Observable ?

public cacheMyServiceResponse(): Observable<any> {
  if(this.cache) {
      return of(this.cache);
  else if(!this.currentCall) {
    this.currentCall = this.appConfig.getEndpoint('myService')
      .pipe(
        switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())),
        tap((body: any) => {
          if (body) { //  this endpoint can also return a 204 (No Content), where body is null
            this.cache = body.myData;
          }
        }),
        take(1),
        catchError(error => {
          this.errorService.trackError(error.status);
          return of(true);
        })
      );
  }
  return this.currentCall;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.