typescript 33 lines · 7 steps

Polling a job until it finishes in Angular

An RxJS pipeline repeatedly hits an API on a timer and completes the moment a job is done or failed.

Explained by highlit
1import { Injectable, inject } from '@angular/core';
2import { HttpClient } from '@angular/common/http';
3import { Observable, timer, throwError } from 'rxjs';
4import { switchMap, takeWhile, filter, take, catchError } from 'rxjs/operators';
5 
6export interface Job {
7 id: string;
8 status: 'queued' | 'running' | 'complete' | 'failed';
9 result?: unknown;
10 error?: string;
11}
12 
13@Injectable({ providedIn: 'root' })
14export class JobPollingService {
15 private readonly http = inject(HttpClient);
16 private readonly intervalMs = 2000;
17 
18 pollUntilComplete(jobId: string): Observable<Job> {
19 return timer(0, this.intervalMs).pipe(
20 switchMap(() => this.http.get<Job>(`/api/jobs/${jobId}`)),
21 takeWhile(job => job.status === 'queued' || job.status === 'running', true),
22 switchMap(job => {
23 if (job.status === 'failed') {
24 return throwError(() => new Error(job.error ?? 'Job failed'));
25 }
26 return [job];
27 }),
28 filter(job => job.status === 'complete'),
29 take(1),
30 catchError(err => throwError(() => err)),
31 );
32 }
33}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1timer plus switchMap turns a schedule into a stream of fresh HTTP requests without manual loops.
  2. 2takeWhile with an inclusive flag lets you emit the final terminal value before completing the stream.
  3. 3Modeling failure as throwError keeps error handling inside the observable pipeline rather than in callbacks.

Related explainers

php
<?php
 
namespace App\Http\Controllers;
 

Handling Stripe webhooks in Laravel

webhooks signature-verification dependency-injection
Intermediate 7 steps
typescript
type Flatten = Record<string, unknown>;
 
function isPlainObject(value: unknown): value is Record<string, unknown> {
  return (

Flattening nested objects into dotted keys

recursion reduce type-guards
Intermediate 7 steps
typescript
import { Injectable, signal, computed, effect, inject } from '@angular/core';
import { DOCUMENT } from '@angular/common';
 
export type Theme = 'light' | 'dark';

A signal-based theme service in Angular

signals reactivity dependency-injection
Intermediate 7 steps
typescript
import { Injectable } from '@angular/core';
import { HttpClient, HttpEventType, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map, distinctUntilChanged, scan } from 'rxjs/operators';

Tracking upload progress in Angular

rxjs http-events state-reduction
Intermediate 8 steps
typescript
import { Component, HostBinding, Input } from '@angular/core';
 
type ProgressVariant = 'success' | 'warning' | 'danger';
 

A CSS-driven progress ring in Angular

host-bindings css-custom-properties input-setters
Intermediate 8 steps
java
@Component
public class RegionCacheWarmer implements SmartInitializingSingleton {
 
    private static final Logger log = LoggerFactory.getLogger(RegionCacheWarmer.class);

Warming a Spring cache at startup

caching startup-hook dependency-injection
Intermediate 7 steps

Share this explainer

Here's the card — post it anywhere.

Polling a job until it finishes in Angular — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code