---
name: angular-http
description: Implement HTTP data fetching in Angular v20+ using resource(), httpResource(), and HttpClient. Use for API calls, data loading with signals, request/response handling, and interceptors. Triggers on data fetching, API integration, loading states, error handling, or converting Observable-based HTTP to signal-based patterns.
---
# Angular HTTP & Data Fetching
Fetch data in Angular using signal-based `resource()`, `httpResource()`, and the traditional `HttpClient`.
## httpResource() - Signal-Based HTTP
`httpResource()` wraps HttpClient with signal-based state management:
```typescript
import { Component, signal } from '@angular/core';
import { httpResource } from '@angular/common/http';
interface User {
id: number;
name: string;
email: string;
}
@Component({
selector: 'app-user-profile',
template: `
@if (userResource.isLoading()) {
Loading...
} @else if (userResource.error()) {
Error: {{ userResource.error()?.message }}
} @else if (userResource.hasValue()) {
{{ userResource.value().name }}
{{ userResource.value().email }}
}
`,
})
export class UserProfileComponent {
userId = signal('123');
// Reactive HTTP resource - refetches when userId changes
userResource = httpResource(() => `/api/users/${this.userId()}`);
}
```
### httpResource Options
```typescript
// Simple GET request
userResource = httpResource(() => `/api/users/${this.userId()}`);
// With full request options
userResource = httpResource(() => ({
url: `/api/users/${this.userId()}`,
method: 'GET',
headers: { 'Authorization': `Bearer ${this.token()}` },
params: { include: 'profile' },
}));
// With default value
usersResource = httpResource(() => '/api/users', {
defaultValue: [],
});
// Skip request when params undefined
userResource = httpResource(() => {
const id = this.userId();
return id ? `/api/users/${id}` : undefined;
});
```
### Resource State
```typescript
// Status signals
userResource.value() // Current value or undefined
userResource.hasValue() // Boolean - has resolved value
userResource.error() // Error or undefined
userResource.isLoading() // Boolean - currently loading
userResource.status() // 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local'
// Actions
userResource.reload() // Manually trigger reload
userResource.set(value) // Set local value
userResource.update(fn) // Update local value
```
## resource() - Generic Async Data
For non-HTTP async operations or custom fetch logic:
```typescript
import { resource, signal } from '@angular/core';
@Component({...})
export class SearchComponent {
query = signal('');
searchResource = resource({
// Reactive params - triggers reload when changed
params: () => ({ q: this.query() }),
// Async loader function
loader: async ({ params, abortSignal }) => {
if (!params.q) return [];
const response = await fetch(`/api/search?q=${params.q}`, {
signal: abortSignal,
});
return response.json() as Promise;
},
});
}
```
### Resource with Default Value
```typescript
todosResource = resource({
defaultValue: [] as Todo[],
params: () => ({ filter: this.filter() }),
loader: async ({ params }) => {
const res = await fetch(`/api/todos?filter=${params.filter}`);
return res.json();
},
});
// value() returns Todo[] (never undefined)
```
### Conditional Loading
```typescript
const userId = signal(null);
userResource = resource({
params: () => {
const id = userId();
// Return undefined to skip loading
return id ? { id } : undefined;
},
loader: async ({ params }) => {
return fetch(`/api/users/${params.id}`).then(r => r.json());
},
});
// Status is 'idle' when params returns undefined
```
## HttpClient - Traditional Approach
For complex scenarios or when you need Observable operators:
```typescript
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({...})
export class UsersComponent {
private http = inject(HttpClient);
// Convert Observable to Signal
users = toSignal(
this.http.get('/api/users'),
{ initialValue: [] }
);
// Or use Observable directly
users$ = this.http.get('/api/users');
}
```
### HTTP Methods
```typescript
private http = inject(HttpClient);
// GET
getUser(id: string) {
return this.http.get(`/api/users/${id}`);
}
// POST
createUser(user: CreateUserDto) {
return this.http.post('/api/users', user);
}
// PUT
updateUser(id: string, user: UpdateUserDto) {
return this.http.put(`/api/users/${id}`, user);
}
// PATCH
patchUser(id: string, changes: Partial) {
return this.http.patch(`/api/users/${id}`, changes);
}
// DELETE
deleteUser(id: string) {
return this.http.delete(`/api/users/${id}`);
}
```
### Request Options
```typescript
this.http.get('/api/users', {
headers: {
'Authorization': 'Bearer token',
'Content-Type': 'application/json',
},
params: {
page: '1',
limit: '10',
sort: 'name',
},
observe: 'response', // Get full HttpResponse
responseType: 'json',
});
```
## Interceptors
### Functional Interceptor (Recommended)
```typescript
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(AuthService);
const token = authService.token();
if (token) {
req = req.clone({
setHeaders: { Authorization: `Bearer ${token}` },
});
}
return next(req);
};
// error.interceptor.ts
export const errorInterceptor: HttpInterceptorFn = (req, next) => {
return next(req).pipe(
catchError((error: HttpErrorResponse) => {
if (error.status === 401) {
inject(Router).navigate(['/login']);
}
return throwError(() => error);
})
);
};
// logging.interceptor.ts
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const started = Date.now();
return next(req).pipe(
tap({
next: () => console.log(`${req.method} ${req.url} - ${Date.now() - started}ms`),
error: (err) => console.error(`${req.method} ${req.url} failed`, err),
})
);
};
```
### Register Interceptors
```typescript
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor,
errorInterceptor,
loggingInterceptor,
])
),
],
};
```
## Error Handling
### With httpResource
```typescript
@Component({
template: `
@if (userResource.error(); as error) {
{{ getErrorMessage(error) }}
}
`,
})
export class UserComponent {
userResource = httpResource(() => `/api/users/${this.userId()}`);
getErrorMessage(error: unknown): string {
if (error instanceof HttpErrorResponse) {
return error.error?.message || `Error ${error.status}: ${error.statusText}`;
}
return 'An unexpected error occurred';
}
}
```
### With HttpClient
```typescript
import { catchError, retry } from 'rxjs';
getUser(id: string) {
return this.http.get(`/api/users/${id}`).pipe(
retry(2), // Retry up to 2 times
catchError((error: HttpErrorResponse) => {
console.error('Error fetching user:', error);
return throwError(() => new Error('Failed to load user'));
})
);
}
```
## Loading States Pattern
```typescript
@Component({
template: `
@switch (dataResource.status()) {
@case ('idle') {