Reputation: 841
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
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.
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()],
});
Upvotes: 2