Angular API Requests with tanstack-fetch and TanStack Query
How do you keep HTTP requests consistent across an Angular application without mixing API transport, server-state management, and UI logic?
One practical option is to use a dedicated HTTP client alongside TanStack Query.
In this tutorial, we'll build a small Angular users screen using tanstack-fetch.
tanstack-fetch is an independent open-source project by Mohammad Garmabi, not an official TanStack package.
Install
npm install tanstack-fetch
npm install @tanstack/angular-query-experimental
Create the client
// api.ts
import { createFetch } from 'tanstack-fetch';
export interface User {
id: number;
name: string;
email: string;
}
export const api = createFetch({
baseUrl: 'https://jsonplaceholder.typicode.com',
});
Provide it through Angular DI
import { bootstrapApplication } from '@angular/platform-browser';
import { provideFetchClient } from 'tanstack-fetch/angular';
import { api } from './api';
import { UsersComponent } from './users.component';
bootstrapApplication(UsersComponent, {
providers: [
provideFetchClient({ client: api }),
],
});
Query data and pass AbortSignal
import { Component } from '@angular/core';
import { injectQuery } from '@tanstack/angular-query-experimental';
import { injectFetch } from 'tanstack-fetch/angular';
import { isFetchError } from 'tanstack-fetch';
import { User } from './api';
@Component({
selector: 'app-users',
standalone: true,
template: `
@if (users.isPending()) {
<p>Loading...</p>
} @else if (users.isError()) {
<p>{{ errorLabel() }}</p>
} @else {
@for (user of users.data(); track user.id) {
<p>{{ user.name }}</p>
}
}
`,
})
export class UsersComponent {
private readonly api = injectFetch();
readonly users = injectQuery(() => ({
queryKey: ['users'],
queryFn: ({ signal }: { signal: AbortSignal }) =>
this.api.get<User[]>('/users', { signal }),
}));
errorLabel(): string {
const error = this.users.error();
return isFetchError(error)
? `HTTP ${error.status}: ${error.message}`
: 'Request failed';
}
}
What's happening here?
There are three distinct responsibilities:
- Angular DI provides the configured HTTP client.
- tanstack-fetch handles the HTTP request and typed response.
- TanStack Query manages query state and caching.
Passing signal matters. A query can be cancelled as its lifecycle changes, and forwarding the AbortSignal lets the HTTP request participate in that cancellation.
Error handling is also explicit: isFetchError() identifies errors produced by the HTTP client, allowing the UI to display an HTTP status where available.
What about authentication and retries?
Treat these as separate design decisions. Configure authentication, retry behavior, and application-specific error policies according to your security and reliability requirements. Don't assume every HTTP error should be retried, and don't automatically treat every authorization response as a reason to log a user out.
When is this useful?
Consider this architecture when your Angular app has multiple API endpoints, shared HTTP configuration, typed data contracts, and a need for consistent cancellation and error handling.
For a small application, Angular's built-in HttpClient may be enough. An additional HTTP client is valuable only when its abstractions solve a real problem in your codebase.
References
What does your Angular application use for API requests: HttpClient, a custom wrapper, or a dedicated fetch client?
Author: Mohammad Garmabi — creator of tanstack-fetch, an independent TypeScript HTTP client.
Top comments (0)