Data Polling on the Frontend for Long-Running HTTP Requests: Angular Example

Yevheniia - Sep 14 - - Dev Community

What is polling, and when does it come into play?

Polling is like checking your mailbox every few minutes, hoping for that important letter. In programming, it refers to repeatedly sending requests to the server at regular intervals to check if new data is available.

Real-world scenario:
Imagine a web app - an internet-shop with both frontend and backend parts. After selecting items, a customer proceeds to the cart and clicks the 'buy' button. When this happens, the frontend sends a /buy HTTP request to the backend. The backend then performs several operations to complete the purchase:

  1. Creating a new order entry.
  2. Updating the database for the selected items.
  3. Handling third-party communications for payment processing.

These actions can take anywhere from a few seconds to a few minutes. To avoid keeping the client waiting for so long, the backend often responds immediately with a 202 Accepted status and a taskId, while continuing to process the request in the background. The frontend's role here is to poll the server at regular intervals using the taskId to check when the data is ready.

In this article, I’ll show you a simple and effective way to implement polling into an Angular frontend app:

**cart.component.ts**

import { Component, OnInit } from '@angular/core';

import { take } from 'rxjs';

import { HttpService } from '../../modules/shared/services/http.service';
import { AuthService } from '../../modules/shared/services/auth.service';

@Component({
    selector: 'app-cart',
    templateUrl: './cart.component.html',
    styleUrl: './cart.component.scss',
})
export class CartComponent implements OnInit {
    constructor(
        private readonly httpService: HttpService,
        private readonly authService: AuthService,
    ) {}

    ngOnInit() {}

    private executePurchase(selectedGoodsIds: Array<string>): void {
        const params = {
            userId: this.authService.currentUserId,
            goods: [...selectedGoodsIds],
        };
        this.httpService
            .executePurchase(params)
            .pipe(take(1))
            .subscribe({
                next: res => {
                    console.log(res); //here your logic
                },
                error: err => console.error(err),
            });
    }
}

**http.service.ts**

import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';

import { Observable, catchError, delay, filter, map, of, startWith, switchMap, timer } from 'rxjs';

import { AppConfig } from '../../../configs/app.config';
import { IResponse } from '../interfaces/response.interface';
import { ParsePurchaseService } from './parse-purchase.service';
import { IPurchase, IExecutePurchaseResponse } from '../interfaces/purchase.interface';

@Injectable({
    providedIn: 'root',
})
export class HttpService {
    constructor(
        private readonly http: HttpClient,
        private readonly parsePurchaseService: ParsePurchaseService,
    ) {}

    public executePurchase(params: IPurchase): Observable<IExecutePurchaseResponse> {
        return this.executePurchaseRequest(params);
    }

    public pollData(url: string, interval: number): Observable<any> {
        return timer(0, interval).pipe(
            startWith(null), // Start with null to avoid immediate emission at the beginning
            delay(interval),
            switchMap(() => this.http.get<any>(url)),
            catchError(error => {
                throw new Error(`Polling error: ${error}`);
            }),
        );
    }

    private executePurchaseRequest(params: IPurchase): Observable<IExecutePurchaseResponse> {
        return this.http.post<IResponse>(`${AppConfig.API_URL}/purchase/execute`, params).pipe(
            switchMap(data => {
                if (data.status === 200) {
                    return of(data).pipe(
                        map(parsedData => this.parsePurchasesService.parseCommitedPurchaseResponse(parsedData.data)),
                    );
                } else if (data.status === 202 && data.data.taskId) {
                    return this.pollData(
                        `${AppConfig.API_URL}/purchase/execution-status/${data.data.taskId}`, //every 5 seconds poll server
                        5000,
                    ).pipe(
                        filter(res => res?.data?.status === 'done'),
                        map(parsedData => this.parsePurchasesService.parseCommitedPurchaseResponse(parsedData.data)),
                    );
                } else {
                    return of(data).pipe(
                        map(parsedData => this.parsePurchasesService.parseCommitedPurchaseResponse(parsedData.data)),
                    );
                }
            }),
            catchError(error => {
                throw error;
            }),
        );
    }
}

Enter fullscreen mode Exit fullscreen mode

Thanks for reading, hope it was useful, please feel free to comment))

. . . .
Terabox Video Player