Search code examples
javascriptangulartypescriptpollingangular-signals

How to perform polling using angular signals and resource API


I have this scenario, where the database is updated regularly, for example say stock market prices (I do not need immediate updates a poll every 2 minutes is good enough).

Using short intervals like 2 or 5 seconds will lead to performance issues so I am choosing 2 minutes as my threshold for polling.

I want to implement a polling mechanism to update the values at the set interval.

Short and long polling

I know it can be done with push based systems, but I want to achieve this with pull/poll based system.

Below it the minimal reproducible code and working stackblitz for reference:

@Component({
  selector: 'app-root',
  imports: [CommonModule],
  template: `
    <div>{{rxResource.value() | json}}</div>
    <hr/>
    <div>{{resource.value() | json}}</div>
  `,
})
export class App {
  http = inject(HttpClient);
  serviceIdSignal: WritableSignal<number> = signal(1);
  rxResource = rxResource({
    request: () => this.serviceIdSignal(),
    loader: ({ request: id }) => {
      return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
    },
  });

  resource = resource({
    request: () => this.serviceIdSignal(),
    loader: ({ request: id }) => {
      return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(
        (res: any) => res.json()
      );
    },
  });
}

The above code does not react/poll, my input signal stays the same, but I need to poll using resource API.

Stackblitz Demo


Solution

  • You can use the rxjs operator interval to trigger for a fixed number of seconds, this will create the polling trigger.

    The interval is an observable, we need to convert it to a signal so that it can be used as an input for resource APIs, for this we use toSignal to convert it.

    // we use rxjs interval to achieve polling of apis, enter the time in milliseconds
    // long polling -> longer time
    // short polling -> shorter time
    // every 2 minutes
    pollSignal = toSignal(interval(120000));
    

    Then we pass this as an input to our resource APIs and polling should happen as expected.

    rxResource = rxResource({
      request: () => ({ id: this.serviceIdSignal(), poll: this.pollSignal() }),
      loader: ({ request: { id } }) => {
        return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
      },
    });
    
    resource = resource({
      request: () => ({ id: this.serviceIdSignal(), poll: this.pollSignal() }),
      loader: ({ request: { id } }) => {
        return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(
          (res: any) => res.json()
        );
      },
    });
    

    We can add a small loader using ResourceStatus to indicate a refresh.

    <div>
      @if(![rs.Loading, rs.Reloading].includes(rxResource.status())) {
        {{rxResource.value() | json}}
      } @else{
        Loading...
      }
      </div>
        <hr/>
        <div>
      @if(![rs.Loading, rs.Reloading].includes(resource.status())) {
        {{resource.value() | json}}
      } @else{
        Loading...
      }
    </div>
    

    Full Code:

    import {
      Component,
      WritableSignal,
      signal,
      resource,
      inject,
      ResourceStatus,
    } from '@angular/core';
    import { bootstrapApplication } from '@angular/platform-browser';
    import { HttpClient, provideHttpClient } from '@angular/common/http';
    import { CommonModule } from '@angular/common';
    import { rxResource, toSignal } from '@angular/core/rxjs-interop';
    import { interval } from 'rxjs';
    @Component({
      selector: 'app-root',
      imports: [CommonModule],
      template: `
        <div>
          @if(![rs.Loading, rs.Reloading].includes(rxResource.status())) {
            {{rxResource.value() | json}}
          } @else{
            Loading...
          }
          </div>
            <hr/>
            <div>
          @if(![rs.Loading, rs.Reloading].includes(resource.status())) {
            {{resource.value() | json}}
          } @else{
            Loading...
          }
        </div>
      `,
    })
    export class App {
      rs = ResourceStatus;
      http = inject(HttpClient);
      serviceIdSignal: WritableSignal<number> = signal(1);
      // we use rxjs interval to achieve polling of apis, enter the time in milliseconds
      // long polling -> longer time
      // short polling -> shorter time
      // every 2 minutes
      pollSignal = toSignal(interval(120000));
      rxResource = rxResource({
        request: () => ({ id: this.serviceIdSignal(), poll: this.pollSignal() }),
        loader: ({ request: { id } }) => {
          return this.http.get(`https://jsonplaceholder.typicode.com/todos/${id}`);
        },
      });
    
      resource = resource({
        request: () => ({ id: this.serviceIdSignal(), poll: this.pollSignal() }),
        loader: ({ request: { id } }) => {
          return fetch(`https://jsonplaceholder.typicode.com/todos/${id}`).then(
            (res: any) => res.json()
          );
        },
      });
    }
    
    bootstrapApplication(App, {
      providers: [provideHttpClient()],
    });
    

    Stackblitz Demo