Setting Up HttpClient
Angular's HttpClient is the standard way to communicate with backend APIs. First, provide it in your application config:
typescript
// app.config.ts
import { provideHttpClient } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
provideHttpClient(),
],
};Making HTTP Requests
GET Request
typescript
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
interface User {
id: number;
name: string;
email: string;
}
@Injectable({ providedIn: 'root' })
export class UserService {
private http = inject(HttpClient);
private apiUrl = 'https://api.example.com/users';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl);
}
getUserById(id: number): Observable<User> {
return this.http.get<User>(`${this.apiUrl}/${id}`);
}
}POST Request
typescript
createUser(user: Omit<User, 'id'>): Observable<User> {
return this.http.post<User>(this.apiUrl, user);
}PUT Request
typescript
updateUser(id: number, user: Partial<User>): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user);
}DELETE Request
typescript
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`);
}PATCH Request
typescript
patchUser(id: number, changes: Partial<User>): Observable<User> {
return this.http.patch<User>(`${this.apiUrl}/${id}`, changes);
}Subscribing to HTTP Observables
HTTP methods return cold Observables — the request is not made until you subscribe:
typescript
@Component({
selector: 'app-user-list',
standalone: true,
template: `
@if (loading) {
<p>Loading...</p>
} @else if (error) {
<p class="error">{{ error }}</p>
} @else {
@for (user of users; track user.id) {
<div>{{ user.name }} — {{ user.email }}</div>
}
}
`,
})
export class UserListComponent implements OnInit {
private userService = inject(UserService);
users: User[] = [];
loading = true;
error = '';
ngOnInit() {
this.userService.getUsers().subscribe({
next: (users) => {
this.users = users;
this.loading = false;
},
error: (err) => {
this.error = 'Failed to load users';
this.loading = false;
console.error(err);
},
});
}
}Using AsyncPipe (Preferred)
typescript
@Component({
selector: 'app-user-list',
standalone: true,
imports: [AsyncPipe],
template: `
@if (users$ | async; as users) {
@for (user of users; track user.id) {
<div>{{ user.name }}</div>
}
} @else {
<p>Loading...</p>
}
`,
})
export class UserListComponent {
private userService = inject(UserService);
users$ = this.userService.getUsers();
}Tip: The async pipe automatically subscribes and unsubscribes, preventing memory leaks.
Query Parameters
typescript
import { HttpParams } from '@angular/common/http';
searchUsers(query: string, page: number = 1): Observable<User[]> {
const params = new HttpParams()
.set('q', query)
.set('page', page.toString())
.set('limit', '20');
return this.http.get<User[]>(this.apiUrl, { params });
// GET /users?q=john&page=1&limit=20
}Custom Headers
typescript
import { HttpHeaders } from '@angular/common/http';
createUser(user: Omit<User, 'id'>): Observable<User> {
const headers = new HttpHeaders({
'Content-Type': 'application/json',
'X-Custom-Header': 'BigXStar',
});
return this.http.post<User>(this.apiUrl, user, { headers });
}Error Handling
Using catchError
typescript
import { catchError, throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl).pipe(
catchError(this.handleError),
);
}
private handleError(error: HttpErrorResponse) {
let errorMessage = 'An unknown error occurred';
if (error.status === 0) {
errorMessage = 'Network error — please check your connection';
} else if (error.status === 404) {
errorMessage = 'Resource not found';
} else if (error.status === 401) {
errorMessage = 'Unauthorized — please log in';
} else if (error.status === 500) {
errorMessage = 'Server error — please try again later';
}
console.error(`Error ${error.status}: ${error.message}`);
return throwError(() => new Error(errorMessage));
}Retry on Failure
typescript
import { retry, catchError } from 'rxjs';
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl).pipe(
retry(3), // Retry up to 3 times on failure
catchError(this.handleError),
);
}HTTP Interceptors
Interceptors let you modify every HTTP request or response globally.
Functional Interceptor (Modern)
typescript
// auth.interceptor.ts
import { HttpInterceptorFn } from '@angular/common/http';
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const token = localStorage.getItem('auth_token');
if (token) {
const clonedReq = req.clone({
setHeaders: {
Authorization: `Bearer ${token}`,
},
});
return next(clonedReq);
}
return next(req);
};Logging Interceptor
typescript
import { HttpInterceptorFn } from '@angular/common/http';
import { tap } from 'rxjs';
export const loggingInterceptor: HttpInterceptorFn = (req, next) => {
const started = Date.now();
console.log(`→ ${req.method} ${req.url}`);
return next(req).pipe(
tap({
next: () => {
const elapsed = Date.now() - started;
console.log(`← ${req.method} ${req.url} (${elapsed}ms)`);
},
error: (err) => {
const elapsed = Date.now() - started;
console.error(`✕ ${req.method} ${req.url} failed (${elapsed}ms)`, err);
},
}),
);
};Registering Interceptors
typescript
// app.config.ts
import { provideHttpClient, withInterceptors } from '@angular/common/http';
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([authInterceptor, loggingInterceptor]),
),
],
};Complete CRUD Service Example
typescript
@Injectable({ providedIn: 'root' })
export class ProductService {
private http = inject(HttpClient);
private baseUrl = 'https://api.example.com/products';
getAll(): Observable<Product[]> {
return this.http.get<Product[]>(this.baseUrl).pipe(
catchError(this.handleError),
);
}
getById(id: number): Observable<Product> {
return this.http.get<Product>(`${this.baseUrl}/${id}`).pipe(
catchError(this.handleError),
);
}
create(product: Omit<Product, 'id'>): Observable<Product> {
return this.http.post<Product>(this.baseUrl, product).pipe(
catchError(this.handleError),
);
}
update(id: number, product: Partial<Product>): Observable<Product> {
return this.http.put<Product>(`${this.baseUrl}/${id}`, product).pipe(
catchError(this.handleError),
);
}
delete(id: number): Observable<void> {
return this.http.delete<void>(`${this.baseUrl}/${id}`).pipe(
catchError(this.handleError),
);
}
search(query: string): Observable<Product[]> {
return this.http.get<Product[]>(this.baseUrl, {
params: new HttpParams().set('q', query),
}).pipe(
catchError(this.handleError),
);
}
private handleError(error: HttpErrorResponse) {
console.error('API Error:', error);
return throwError(() => error);
}
}Next Steps
With HTTP communication working, you need a way to navigate between different views. Next, we'll explore Angular routing — how to map URLs to components and build multi-page navigation.