Search code examples
angulartypescriptrxjspolling

Dynamically changed polling interval


I need my frontend application to poll my backend application and refresh the displayed data. I solved it in a certain way, but I have a feeling that it could be optimized. Here's my solution:

        this.pollDataSource$ = new BehaviorSubject<number>(this.pollingInterval);

        this.pollDataSource$
            .pipe(
                switchMap(duration =>
                    of(duration)
                        .pipe(
                            filter(duration => duration !== 0),
                            delay(duration),
                            concatMap(() => this.kubernetesObjectList$)
                        )
                )
            )
            .subscribe({
                next: (kubernetesObjectList) => {
                    this.dataSource.data = kubernetesObjectList;
                    if (this.pollingInterval > 0) {
                        this.pollDataSource$.next(this.pollingInterval);
                    }
                }
            });

        this.pollDataSource$.next(-1);

So, I have a drop-down selector which affects this.pollingInterval. Also, I have this.pollDataSource$ which is a BehaviorSubject<number>. It emits a number which is being used as the duration before the next poll.

When this.pollDataSource$ emits -1 (it happens when the user clicks the Refresh button), the data source must be polled immediately, disregarding what polling interval is set.

When this.pollDataSource$ emits some positive number (it happens when a user selects a certain polling interval from the drop-down selector), this number must be used as the duration before the next refresh.

When this.pollDataSource$ emits 0 (it happens when a user selects the Stop Polling option in the same drop-down selector), we must stop polling until the user selects a new polling interval.

And things work just perfectly. The page is loaded, this.pollingInterval has 10000 by default, so the user gets the data immediately, but in 10 seconds it's being updated. When the user hits Refresh, the data is being updated and the next automatic refresh happens after 10 seconds since that. When the user switches to Stop Polling, the data stay still. When the user switches to another interval, the data is being updated again. Everything's great! But I have a feeling that my solution is not optimal. I just don't like that construction: ...pipe...switchMap...of...pipe... Is there a way to simplify that?

Thanks in advance for all the clues.


Solution

  • The reason I was mentioning expand is that this operator can be used instead of the construct

    someSubject.pipe(
      // do stuff
    ).subscribe(
      next: data => {
        // do some more stuff
        someSubject.next(something)
      }
    

    This is the construct you are actually using, therefore the suggestion about expand.

    Then, getting back to your question without introducing expand, what you may consider is something like

    this.pollDataSource$
            .pipe(
                switchMap(duration =>
                    return duration === 0 ?
                      NEVER :
                      this.kubernetesObjectList$.pipe(delay(duration));
                )
            )
            .subscribe({
                next: (kubernetesObjectList) => {
                    this.dataSource.data = kubernetesObjectList;
                    if (this.pollingInterval > 0) {
                        this.pollDataSource$.next(this.pollingInterval);
                    }
                }
            });
    
        this.pollDataSource$.next(-1);
    

    With expand it could look like

    this.pollDataSource$
                .pipe(
                    expand(duration =>
                        return duration === 0 ?
                          NEVER :
                          this.kubernetesObjectList$.pipe(
                             delay(duration),
                             map(() =>  duration)
                          );
                    )
                )
                .subscribe({
                    next: (kubernetesObjectList) => {
                        this.dataSource.data = kubernetesObjectList;
                    }
                });
    
            this.pollDataSource$.next(-1);
    

    Whether these versions of the code are cleaner that your original one, which I found clear and readable, is probably a matter of personal taste.

    All the "could" and "would" I have used are due to the fact that I do not have a playground where to test this code, so it is very much possible that you will find something not working here.