DEV Community

Tsveta Brakalova
Tsveta Brakalova

Posted on

Signal-based polling in Angular

I recently had to introduce polling for file download jobs in a project. The idea was to show a table of queued jobs and poll for updates to any of the jobs. Each job would have a progress bar showing the current progress returned by the backend and once the job is ready the job would be available for download via a download button.
My initial idea was to use RxJS with a BehaviorSubject

// Used to manually trigger polling
private startPolling$ = new BehaviorSubject<void>(undefined);

// Initialize polling stream
ngOnInit() {
  this.startPolling$.pipe(
    switchMap(() =>
      interval(this.POLLING_INTERVAL).pipe(
        tap(() => this.jobsResource.reload()),         // 🔁 Polling action
        takeUntil(this.jobsFinished$)               // ✅ Stop when done 
      )
    ),
    takeUntilDestroyed(this.destroyRef)
  ).subscribe();
}

// Manually trigger polling (e.g., after export or pagination change)
triggerPolling() {
  this.startPolling$.next();
}

Enter fullscreen mode Exit fullscreen mode

startPolling$: Manual trigger stream to control when polling should start or restart.

switchMap: Replaces any previous polling stream with a new one whenever a new trigger is emitted.

this.jobsResource.reload(): Performs the actual data reload on each polling tick. The jobsResource as the name suggest is an rxResource calling a backend service.

takeUntil(this.allJobsFinished$): Stops polling automatically when jobs are detected as complete. It relies on the following linkedSignal:

  jobsFinished = linkedSignal({
    source: this.jobsResource.status,
    computation: (status: ResourceStatus) => {
      if (status === ResourceStatus.Resolved) {
        return this.jobs()?.every(j => j.isReady)
      }
      return false
    }
  })
Enter fullscreen mode Exit fullscreen mode

This linked signal tracks job completion, derived from the resource's status (a part of the rxResource API to indicate the state of completion). Once the resource status is resolved and data is present, I check whether all jobs have been completed and return a boolean value.

So, with these two pieces of logic in my code, the user will enter the view and if the jobs are not done, the polling will start right away. I also had logic to retriger it with a button.
This worked great when first rendered but when I would paginate between table pages, the logic to restart polling the jobs didn't work properly.
At this point I decided that my code was more complex than necessary and decided that it would be easier to just directly rely on the resource signal completing without all the bloated logic with observables.

I decided to create an effect with the following logic:

  pollingEffect = effect(() => {
    const done = this.jobsFinished()

    if (this.pollingIntervalId) {
      clearInterval(this.pollingIntervalId)
      this.pollingIntervalId = null
    }

    if (!done) {
      this.pollingIntervalId = setInterval(() => {
        this.jobsResource.reload()
      }, this.POLLING_INTERVAL)
    }
  })

Enter fullscreen mode Exit fullscreen mode

Basically this creates a new polling interval and executes it each time the jobs are not finished.

RxJS is still extremely powerful — and in many cases, still the right tool. But for UI-driven polling where state changes are clearly reactive, Angular Signals provide a simpler, cleaner alternative.
The only drawback here could be the use of native JS APIs and the manual cleanup.

Top comments (0)