MageWind
MageWind

Reputation: 841

Using Angular v18 resource with HttpClient

I'm trying to update my code to use the new Resource type. The Angular documentation for Resource has this example:

const userId: Signal<string> = getUserId();
const userResource = resource({
  request: () => ({id: userId()}),
  loader: ({request, abortSignal}): Promise<User> => {
    // fetch cancels any outstanding HTTP requests when the given `AbortSignal`
    // indicates that the request has been aborted.
    return fetch(`users/${request.id}`, {signal: abortSignal});
  },
});

My question is what's the proper way to use the abortSignal when making requests using HttpClient (i.e. instead of fetch from the snippet)?

Upvotes: 3

Views: 495

Answers (1)

Naren Murali
Naren Murali

Reputation: 57986

You have to use the rxResource which is designed specifically for rxjs and httpClient (Since it returns observables), The subscription are auto unsubscribed, so you do not need abortClient at all. Instead use the set method which is present inside the object returned by the rxResource instead. This set method will interrupt the API call, but setting the default value and stops all pending requests.


set

Convenience wrapper for value.set.

@param -> valueT | undefined @returns -> void


In the below example, we make an API call using rxResource. Then using the returned resourceRef we call the set method which stops the API call and it can accept new requests going forward.


One more thing you need to know about rxResource:

The fetch API does not cancel pending requests when new requests arrive, hence you have to provide the AbortSignal to achieve this behavior. The rxResource does not have this problem, it is inherently a switchMap like behavior (cancels previous pending requests).


In the below working example, the rxResource makes the API call, by calling the abort button, we set a value manually and this value, will cancel the API call, but you need to do this, very quickly to see it in action (open debugger and network tab) type a number and then give abort immediately.

import {
  Component,
  inject,
  model,
  ResourceRef,
  ResourceStatus,
  signal,
  Signal,
} from '@angular/core';
import { rxResource } from '@angular/core/rxjs-interop';
import { bootstrapApplication } from '@angular/platform-browser';
import { HttpClient, provideHttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { delay } from 'rxjs';
@Component({
  selector: 'app-root',
  imports: [CommonModule, FormsModule],
  template: `
    <input [(ngModel)]="id"/>
    <button (click)="abort()">Abort Request</button>
    <br/>
    @if(apiRef.status() === rs.Resolved) {
      {{apiRef.value() | json}}
    } @else if (apiRef.status() === rs.Loading) {
      Is Loading...
    } @else if (apiRef.status() === rs.Idle) {
      Is Idle...
    }
  `,
})
export class App {
  rs = ResourceStatus;
  id: Signal<string> = model('1');
  http: HttpClient = inject(HttpClient);
  name = 'Angular';
  apiRef: ResourceRef<any> = rxResource({
    request: () => ({ id: this.id() }),
    loader: ({ request: { id } }: any) =>
      this.http
        .get(`https://jsonplaceholder.typicode.com/todos/${id}`)
        .pipe(delay(3000)),
  });

  abort() {
    this.apiRef.set([]); // <- stops the API call, but you need to do it really fast.
  }
}

bootstrapApplication(App, {
  providers: [provideHttpClient()],
});

Stackblitz Demo

Upvotes: 2

Related Questions