angular-http

安装量: 3K
排名: #723

安装

npx skills add https://github.com/analogjs/angular-skills --skill angular-http

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 UserProfileComponent { userId = signal('123');

// Reactive HTTP resource - refetches when userId changes userResource = httpResource(() => /api/users/${this.userId()}); }

httpResource Options // 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 // 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 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<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(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 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 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 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) // 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 // 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 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 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 @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 DataComponent { query = signal(''); dataResource = httpResource(() => this.query() ? /api/search?q=${this.query()} : undefined ); }

For advanced patterns, see references/http-patterns.md.

返回排行榜