poll

In modern web applications, it's common to interact with APIs to fetch data, and sometimes, we need to continuously poll these APIs to keep our data up-to-date. While there are various ways to implement polling mechanisms, leveraging the power of Reactive Programming can offer an elegant and efficient solution. Hence, the poll operator, a handy tool in RxJS for simplifying the process of polling APIs comes to play.

The poll operator is a custom operator built on top of RxJS, a powerful library for reactive programming in JavaScript. It allows developers to create observables that periodically emit data at specified intervals.

  • Simplicity: It abstracts away the complexity of managing polling intervals and API requests.
  • Flexibility: Developers can customize parameters, such as polling intervals and request payloads, to suit their application's needs.
  • Reactivity: It seamlessly integrates with other RxJS operators, enabling developers to build reactive pipelines for handling data streams.

Usage

poll operator is especially handy when fetching data periodically from an API endpoint. This is useful for scenarios where real-time updates are required but the server does not support push notifications.

By default, the poll operator starts polling immediately. Please refer to section if you'd like to start polling after an action.

The poll operator takes an options object containing essential parameters:

  • pollingFn: A function to be executed periodically, that returns an Observable, Promise or a primitive value.
  • interval: The interval in milliseconds between each poll.
  • paramsBuilder: An optional function that builds parameters for the polling function.
  • forceRefresh: An optional Observable or Signal that forces an immediate execution of the pollingFn function when emitted.
  • initialValue: An optional value as the initial value of the poll results. You can use it if you don't want to display a loading: true state.
  • delay: An optional value if you'd like to delay the start of polling. By default, the polling starts immediately.

Example

Poll with different types of polling function

Polling function supports Promise, Observable, or primitive value.

import { poll } from 'ngx-lift';
import { ajax } from 'rxjs/ajax';

poll({
  interval: 5000, // Poll every 5 second
  pollingFn: () => Math.random(), // return a primitive value
}).subscribe(console.log);

poll({
  interval: 5000,
  pollingFn: () =>
    new Promise((resolve) => {
      setTimeout(() => { resolve(Math.random()); }, 200); // return a promise
    }),
}).subscribe(console.log);

poll({
  interval: 5000,
  pollingFn: () => ajax('https://api.example.com/data'), // return an observable
}).subscribe(console.log);

Poll Until Condition is Met

Poll every 1 second until the output is greater than 8.

import { poll } from 'ngx-lift';
import { of, takeWhile, delay } from 'rxjs';

poll({
  interval: 1000,
  pollingFn: () => of(Math.random() * 10).pipe(delay(300)),
})
  .pipe(takeWhile((state) => state.data === null || state.data <= 8, true))
  .subscribe(console.log);

Poll with initialValue

By default the poll initial result is {loading: true, error: null, data: null}. It's convenient to display a spinner since loading is true. However, in some cases, you may not want to display a loading spinner. You can set initialValue to any value you want.

import { poll } from 'ngx-lift';
import { of, delay } from 'rxjs';

poll({
  interval: 1000,
  pollingFn: () => of(Math.random() * 10).pipe(delay(300)),
  initialValue: { loading: false, error: null, data: 0 }, // display 0 for initial value
}).subscribe(console.log);

Defer polling

By default, the polling immediately starts. We support delay prop to delay the start of polling.

import { poll } from 'ngx-lift';
import { of } from 'rxjs';

poll({
  interval: 1000,
  pollingFn: () => of(Math.random() * 10),
  delay: 3000, // delay 3s and start polling
}).subscribe(console.log);

Sometime you may want to start the polling after an action. poll operator can work with other RxJS operators to achieve this.

const startPolling = new Subject<void>();

// start button click
function start() {
  startPolling.next();
}

startPolling
  .pipe(
    switchMap(() => {
      return poll({
        interval: 1000,
        pollingFn: () => of(Math.random() * 10).pipe(delay(300)),
      }).pipe(takeWhile((state) => state.data === null || state.data <= 8, true));
    }),
  )
  .subscribe(console.log);

Advanced Usage

  • Force Refresh: Use the forceRefresh observable or signal to trigger the execution of pollingFn whenever necessary, such as in response to user actions. The emitted value from forceRefresh will be passed as the input to paramsBuilder, if provided; otherwise, it goes directly to pollingFn.
  • Custom Parameters: Customize parameters by providing a paramsBuilder function, which is especially useful when the forceRefresh value does not match the structure of pollingFn parameters. This function allows you to transform the input, ensuring it meets the specific requirements of pollingFn.

Advanced Example

The datagrid below will automatically refresh every 10 seconds. Additionally, whenever there's a change in the input, triggering filtering, sorting, or pagination, the API will be promptly called, accompanied by a spinner indicating loading.

Single selection header
Use left or right key to resize the column
Use left or right key to resize the column
Use left or right key to resize the column
Use left or right key to resize the column
 / 10
import {ClarityModule, ClrDatagridStateInterface} from '@clr/angular';
import {AlertComponent, convertToHttpParams, dgState, PageContainerComponent} from 'clr-lift';
import {AsyncState, isEqual, poll} from 'ngx-lift';

export class PollComponent {
  userService = inject(UserService);

  private dgBS = new BehaviorSubject<ClrDatagridStateInterface | null>(null);
  private dgState$ = this.dgBS.pipe(dgState(false));

  usersState$ = poll({
    interval: 10_000,
    pollingFn: (params) => this.userService.getUsers({...params, results: 10, seed: 'abc'}),
    paramsBuilder: (dgState: ClrDatagridStateInterface | null) => convertToHttpParams(dgState), // build params for getUsers
    forceRefresh: this.dgState$,  // datagrid filter, sort, pagination change will immediately force a refresh of the API call
  });
}