<< All versions
Skill v1.0.1
currentLLM-judged scan100/100analogjs/angular-skills/angular-http
2 files
──Details
PublishedMay 15, 2026 at 07:47 AM
Content Hashsha256:4e6db129742b251a...
Git SHA610c90eb9490
Bump Typepatch
──Files
Files (1 file, 8.5 KB)
SKILL.md8.5 KBactive
SKILL.md · 368 lines · 8.5 KB
version: "1.0.1" 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()) {<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 changesuserResource = httpResource<User>(() => `/api/users/${this.userId()}`);}
httpResource Options
typescript
// Simple GET requestuserResource = httpResource<User>(() => `/api/users/${this.userId()}`);// With full request optionsuserResource = httpResource<User>(() => ({url: `/api/users/${this.userId()}`,method: 'GET',headers: { 'Authorization': `Bearer ${this.token()}` },params: { include: 'profile' },}));// With default valueusersResource = httpResource<User[]>(() => '/api/users', {defaultValue: [],});// Skip request when params undefineduserResource = httpResource<User>(() => {const id = this.userId();return id ? `/api/users/${id}` : undefined;});
Resource State
typescript
// Status signalsuserResource.value() // Current value or undefineduserResource.hasValue() // Boolean - has resolved valueuserResource.error() // Error or undefineduserResource.isLoading() // Boolean - currently loadinguserResource.status() // 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' | 'local'// ActionsuserResource.reload() // Manually trigger reloaduserResource.set(value) // Set local valueuserResource.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 Search {query = signal('');searchResource = resource({// Reactive params - triggers reload when changedparams: () => ({ q: this.query() }),// Async loader functionloader: 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
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<string | null>(null);userResource = resource({params: () => {const id = userId();// Return undefined to skip loadingreturn 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 Users {private http = inject(HttpClient);// Convert Observable to Signalusers = toSignal(this.http.get<User[]>('/api/users'),{ initialValue: [] });// Or use Observable directlyusers$ = this.http.get<User[]>('/api/users');}
HTTP Methods
typescript
private http = inject(HttpClient);// GETgetUser(id: string) {return this.http.get<User>(`/api/users/${id}`);}// POSTcreateUser(user: CreateUserDto) {return this.http.post<User>('/api/users', user);}// PUTupdateUser(id: string, user: UpdateUserDto) {return this.http.put<User>(`/api/users/${id}`, user);}// PATCHpatchUser(id: string, changes: Partial<User>) {return this.http.patch<User>(`/api/users/${id}`, changes);}// DELETEdeleteUser(id: string) {return this.http.delete<void>(`/api/users/${id}`);}
Request Options
typescript
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 HttpResponseresponseType: 'json',});
Interceptors
Functional Interceptor (Recommended)
typescript
// auth.interceptor.tsimport { 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.tsexport 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.tsexport 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.tsimport { 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) {<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
typescript
import { catchError, retry } from 'rxjs';getUser(id: string) {return this.http.get<User>(`/api/users/${id}`).pipe(retry(2), // Retry up to 2 timescatchError((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') {<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.