HTTP requests from the backbone of modern web application, allowing them to communicate with servers and fetch or send data.
Angular provide a powerful HTTP client that makes working with HTTP request straightforward and efficient.
Setting Up HTTP Client
Before making HTTP requests, we need to import the HttpClientModule in out application’s root module:
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
HttpClientModule
],
// ... other module configurations
})
export class AppModule { }
Creating a Service
It’s a best practice to encapsulate HTTP requests within services. Here’s an example of a service that handles user-related HTTP requests:
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class UserService {
private apiUrl = 'https://api.example.com/users';
constructor(private http: HttpClient) { }
// GET request
getUsers(): Observable<User[]> {
return this.http.get<User[]>(this.apiUrl)
.pipe(
catchError(this.handleError)
);
}
// POST request
createUser(user: User): Observable<User> {
const headers = new HttpHeaders().set('Content-Type', 'application/json');
return this.http.post<User>(this.apiUrl, user, { headers })
.pipe(
catchError(this.handleError)
);
}
// PUT request
updateUser(id: number, user: User): Observable<User> {
return this.http.put<User>(`${this.apiUrl}/${id}`, user)
.pipe(
catchError(this.handleError)
);
}
// DELETE request
deleteUser(id: number): Observable<void> {
return this.http.delete<void>(`${this.apiUrl}/${id}`)
.pipe(
catchError(this.handleError)
);
}
// Error handling
private handleError(error: any) {
let errorMessage = 'An error occurred';
if (error.error instanceof ErrorEvent) {
// Client-side error
errorMessage = `Error: ${error.error.message}`;
} else {
// Server-side error
errorMessage = `Error Code: ${error.status}\nMessage: ${error.message}`;
}
return throwError(() => errorMessage);
}
}
Using HTTP Parameters and Headers
Sometimes we need to add query parameters or custom headers to our requests:
// Adding query parameters
getFilteredUsers(age: number, city: string): Observable<User[]> {
const params = new HttpParams()
.set('age', age.toString())
.set('city', city);
return this.http.get<User[]>(this.apiUrl, { params })
.pipe(
catchError(this.handleError)
);
}
// Adding custom headers
getProtectedResource(): Observable<any> {
const headers = new HttpHeaders()
.set('Authorization', 'Bearer ' + this.authToken)
.set('Custom-Header', 'custom-value');
return this.http.get(this.apiUrl, { headers })
.pipe(
catchError(this.handleError)
);
}
Using the Service in Components
Here’s how to use the service in a component:
@Component({
selector: 'app-user-list',
template: `
<div *ngIf="users$ | async as users">
<div *ngFor="let user of users">
{{ user.name }}
</div>
</div>
`
})
export class UserListComponent implements OnInit {
users$: Observable<User[]>;
constructor(private userService: UserService) {
this.users$ = this.userService.getUsers();
}
addUser(user: User) {
this.userService.createUser(user).subscribe({
next: (response) => {
console.log('User created successfully', response);
// Handle success
},
error: (error) => {
console.error('Error creating user', error);
// Handle error
}
});
}
}
Handling Different Response Types
Angular’s HTTP client supports various response types:
// Get response as JSON (default)
getData(): Observable<any> {
return this.http.get(this.apiUrl);
}
// Get full response including headers
getData(): Observable<HttpResponse<any>> {
return this.http.get(this.apiUrl, { observe: 'response' });
}
// Get response as text
getText(): Observable<string> {
return this.http.get(this.apiUrl, { responseType: 'text' });
}
// Get response as blob (for files)
getFile(): Observable<Blob> {
return this.http.get(this.apiUrl, { responseType: 'blob' });
}
Implementing Interceptors
HTTP interceptors allow you to modify requests or responses globally:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
intercept(
request: HttpRequest<any>,
next: HttpHandler
): Observable<HttpEvent<any>> {
// Clone and modify the request
const authReq = request.clone({
headers: request.headers.set('Authorization', 'Bearer ' + this.getToken())
});
return next.handle(authReq);
}
private getToken(): string {
// Get token from storage
return localStorage.getItem('token') || '';
}
}
Register the interceptor in your module:
Using NgModule Providers
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';
@NgModule({
// ...
providers: [
{
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptor,
multi: true
}
]
})
export class AppModule { }
Using providedIn Root with Factory Function
import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';
// Define interceptor as a function
export const authInterceptor: HttpInterceptorFn = (req, next) => {
const authReq = req.clone({
headers: req.headers.set('Authorization', `Bearer ${getToken()}`)
});
return next(authReq);
};
// In your app.config.ts or similar
export const appConfig: ApplicationConfig = {
providers: [
provideHttpClient(
withInterceptors([authInterceptor])
)
]
};
Best Practices
- Error Handling: Always implement proper error handling using
catchErroroperator. - Type Safety: Use interfaces to define the shape of your response data.
- Cancellation: Use
takeUntiloperator to cancel ongoing requests when components are destroyed. - Loading States: Implement loading indicators for better user experience.
- Retry Logic: Use
retryoperator for transient failures.