Angular: How to unsubscribe when requesting periodically

Viewed 419

I have an angular component where I request values from a service periodically:

setInterval(() => {
      this.updateValues();
    }, 1000);


updateValues(): void {
  if (this.isValuesUpdateRunning === false) {
    this.isValuesUpdateRunning = true;
    this.apiService.getCurrentValues().subscribe((data: any) => {
      if (data != null) {
        this.currentValues = data;
        this.isValuesUpdateRunning = false;
      }
    });
  }
}

I'm pretty sure that I should unsubscribe from the service somewhere because I do open a lot of subscriptions by doing this. I was thinking about using async pipe, but I need to set the "isValueUpdateRunning" variable and that wouldn't be possible if I did just use async within the template.

Also, unsubscribing within the "onDestroy" method provided by Angular doesn't help either because as far as I understand, this method only gets called when I leave the page. But I need to close my subscriptions more often to prevent keeping thousands of subscriptions open.

I also tried closing the subscription immediately after setting my values, but then I'm not getting any values within my view:

updateValues(): void {
  if (this.isValuesUpdateRunning === false) {
    this.isValuesUpdateRunning = true;
    const sub = this.apiService.getCurrentValues().subscribe((data: any) => {
      if (data != null) {
        this.currentValues = data;
        this.isValuesUpdateRunning = false;
      }
    });
    // closing the subscription right here:
    sub.unsubscribe();
  }
}

Can you help please? I'm running out of ideas.

3 Answers

You can close the subscription once you get the response like as shown below. Also please note that HTTPObservable are autoclosable if you are calling AJAX API from the getCurrentValues() you need not to close the subscription as once you get the http response subscription is completed by the angular HTTP Client.

 updateValues(): void {
      if (this.isValuesUpdateRunning === false) {
        this.isValuesUpdateRunning = true;
        const sub = this.apiService.getCurrentValues().subscribe((data: any) => {
          if (data != null) {
            this.currentValues = data;
            this.isValuesUpdateRunning = false;
          }
         // closing the subscription right here:
         sub.unsubscribe();
        }, error => { sub.unsubscribe});

      }
    }

You don't need to unsubscribe from the service as @Kurt Hamilton mentioned if its using the angular http client. You need to stop the set interval which is creating the subscriptions.

Example using rxjs operators.

interval(1000)
   .pipe(
     takeWhile(() => this.isValuesUpdateRunning), // call subscription until the condition is false
     switchMap(() => this.apiService.getCurrentValues()),
     filter(Boolean), // check if response is not null
     tap(resp => { this.data = resp; this.isValuesUpdateRunning = false; }),
     // handle errors 

Presuming that you are rendering currentValues in the template I recommend use of the async pipe.

In terms of the isValuesUpdateRunning logic, you can use exhaustMap and replace setTimeout with an RxJS interval

I've put a demo below - I highly recommend learning to mock an api service and try these things out for yourself.

const currentValues$ = interval(1000)
  .pipe(
    tap(i => console.log(`timer::${i}`)),
    exhaustMap(i => apiService.getCurrentValues(i)),  // ignores new values until api returns
    tap(apiData => console.log(`api::${apiData}`)),
    filter(data => !!data)                            // filter for truthy values
    takeUntil(stopClick$),                            // or this.destroyed$ component logic
  )

(this assumes async pipe used in template e.g.{{ currentValues$ | async | json }})

update - best to put takeUntil last!!

Stackblitz

References

exhaustmap - ignores new emitted values until current observable completes (angular http requests automatically complete)

Related