angular-http
Signal-based HTTP data fetching with httpResource(), resource(), and HttpClient for Angular v20+.
httpResource()provides reactive HTTP requests with automatic refetching when dependencies change, built-in loading/error states, and manual reload/set/update actionsresource()handles generic async operations with conditional loading, abort signal support, and customizable default values- Functional interceptors for authentication, error handling, and logging integrate via
withInterceptors()in app configuration - Status-driven UI patterns using
@switchonstatus()signal ('idle', 'loading', 'reloading', 'resolved', 'error') simplify loading states and error recovery
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:
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()) {
<p>Loading...</p>
} @else if (userResource.error()) {
<p>Error: {{ userResource.error()?.message }}</p>
<button (click)="userResource.reload()">Retry</button>
} @else if (userResource.hasValue()) {
<h1>{{ userResource.value().name }}</h1>
<p>{{ userResource.value().email }}</p>
}
`,
})
export class UserProfile {
userId = signal('123');
// Reactive HTTP resource - refetches when userId changes
userResource = httpResource<User>(() => `/api/users/${this.userId()}`);
}
httpResource Options
// Simple GET request
userResource = httpResource<User>(() => `/api/users/${this.userId()}`);
// With full request options
userResource = httpResource<User>(() => ({
url: `/api/users/${this.userId()}`,
method: 'GET',
headers: { 'Authorization': `Bearer ${this.token()}` },
params: { include: 'profile' },
}));
// With default value
usersResource = httpResource<User[]>(() => '/api/users', {
defaultValue: [],
});
// Skip request when params undefined
userResource = httpResource<User>(() => {
const id = this.userId();
return id ? `/api/users/${id}` : undefined;
});
Resource State
// 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:
import { resource, signal } from '@angular/core';
@Component({...})
export class Search {
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<SearchResult[]>;
},
});
}
Resource with Default Value
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
const userId = signal<string | null>(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:
import { Component, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { toSignal } from '@angular/core/rxjs-interop';
@Component({...})
export class Users {
private http = inject(HttpClient);
// Convert Observable to Signal
users = toSignal(
this.http.get<User[]>('/api/users'),
{ initialValue: [] }
);
// Or use Observable directly
users$ = this.http.get<User[]>('/api/users');
}
HTTP Methods
private http = inject(HttpClient);
// GET
getUser(id: string) {
return this.http.get<User>(`/api/users/${id}`);
}
// POST
createUser(user: CreateUserDto) {
return this.http.post<User>('/api/users', user);
}
// PUT
updateUser(id: string, user: UpdateUserDto) {
return this.http.put<User>(`/api/users/${id}`, user);
}
// PATCH
patchUser(id: string, changes: Partial<User>) {
return this.http.patch<User>(`/api/users/${id}`, changes);
}
// DELETE
deleteUser(id: string) {
return this.http.delete<void>(`/api/users/${id}`);
}
Request Options
this.http.get<User[]>('/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)
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
import { inject } from '@angular/core';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authService = inject(Auth);
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
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([
authInterceptor,
errorInterceptor,
loggingInterceptor,
])
),
],
};
Error Handling
With httpResource
@Component({
template: `
@if (userResource.error(); as error) {
<div class="error">
<p>{{ getErrorMessage(error) }}</p>
<button (click)="userResource.reload()">Retry</button>
</div>
}
`,
})
export class UserCmpt {
userResource = httpResource<User>(() => `/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
import { catchError, retry } from 'rxjs';
getUser(id: string) {
return this.http.get<User>(`/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
@Component({
template: `
@switch (dataResource.status()) {
@case ('idle') {
<p>Enter a search term</p>
}
@case ('loading') {
<app-spinner />
}
@case ('reloading') {
<app-data [data]="dataResource.value()" />
<app-spinner size="small" />
}
@case ('resolved') {
<app-data [data]="dataResource.value()" />
}
@case ('error') {
<app-error
[error]="dataResource.error()"
(retry)="dataResource.reload()"
/>
}
}
`,
})
export class Data {
query = signal('');
dataResource = httpResource<Data[]>(() =>
this.query() ? `/api/search?q=${this.query()}` : undefined
);
}
For advanced patterns, see references/http-patterns.md.
More from analogjs/angular-skills
angular-component
Create modern Angular standalone components following v20+ best practices. Use for building UI components with signal-based inputs/outputs, OnPush change detection, host bindings, content projection, and lifecycle hooks. Triggers on component creation, refactoring class-based inputs to signals, adding host bindings, or implementing accessible interactive components.
7.6Kangular-signals
Implement signal-based reactive state management in Angular v20+. Use for creating reactive state with signal(), derived state with computed(), dependent state with linkedSignal(), and side effects with effect(). Triggers on state management questions, converting from BehaviorSubject/Observable patterns to signals, or implementing reactive data flows.
5.6Kangular-forms
Build signal-based forms in Angular v21+ using the new Signal Forms API. Use for form creation with automatic two-way binding, schema-based validation, field state management, and dynamic forms. Triggers on form implementation, adding validation, creating multi-step forms, or building forms with conditional fields. Signal Forms are experimental but recommended for new Angular projects. Don't use for template-driven forms without signals or third-party form libraries like Formly or ngx-formly.
4.8Kangular-routing
Implement routing in Angular v20+ applications with lazy loading, functional guards, resolvers, and route parameters. Use for navigation setup, protected routes, route-based data loading, and nested routing. Triggers on route configuration, adding authentication guards, implementing lazy loading, or reading route parameters with signals.
4.7Kangular-di
Implement dependency injection in Angular v20+ using inject(), injection tokens, and provider configuration. Use for service architecture, providing dependencies at different levels, creating injectable tokens, and managing singleton vs scoped services. Triggers on service creation, configuring providers, using injection tokens, or understanding DI hierarchy.
4.2Kangular-directives
Create custom directives in Angular v20+ for DOM manipulation and behavior extension. Use for attribute directives that modify element behavior/appearance, structural directives for portals/overlays, and host directives for composition. Triggers on creating reusable DOM behaviors, extending element functionality, or composing behaviors across components. Note - use native @if/@for/@switch for control flow, not custom structural directives.
4.1K