Skip to main content

Angular HTTP Requests

Introduction

In modern web applications, communication with backend services is essential. Angular provides a powerful HTTP client that allows your application to communicate with backend services using the HTTP protocol. The Angular HTTP client offers a simplified API for making HTTP requests and handling responses in a reactive way using RxJS Observables.

In this tutorial, you'll learn how to:

  • Set up the HttpClient in your Angular application
  • Make various types of HTTP requests (GET, POST, PUT, DELETE)
  • Handle HTTP responses and errors
  • Configure request headers and parameters
  • Work with real-world examples

Prerequisites

Before we begin, make sure you have:

  • Basic knowledge of Angular
  • Understanding of TypeScript
  • Familiarity with Observables and RxJS concepts (helpful but not required)

Setting Up HttpClient

To use Angular's HTTP capabilities, you first need to import the HttpClientModule into your application's module.

typescript
// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http'; // Import HttpClientModule
import { AppComponent } from './app.component';

@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule // Add HttpClientModule to imports
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }

Once the module is imported, you can inject the HttpClient service into your components or services.

Creating a Service for HTTP Requests

It's a best practice to create a dedicated service for HTTP operations rather than placing them directly in components. This promotes code reusability and separation of concerns.

Let's create a data service for a book application:

typescript
// book.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry } from 'rxjs/operators';
import { Book } from './book.model';

@Injectable({
providedIn: 'root'
})
export class BookService {
private apiUrl = 'https://api.example.com/books';

constructor(private http: HttpClient) { }

// Methods will be added here
}

Next, let's define an interface for our data model:

typescript
// book.model.ts
export interface Book {
id: number;
title: string;
author: string;
year: number;
genre: string;
}

Making HTTP GET Requests

The GET request is used to retrieve data from a server. Let's implement methods to fetch books:

typescript
// Inside BookService
// Get all books
getBooks(): Observable<Book[]> {
return this.http.get<Book[]>(this.apiUrl)
.pipe(
retry(1),
catchError(this.handleError)
);
}

// Get a single book by id
getBook(id: number): Observable<Book> {
return this.http.get<Book>(`${this.apiUrl}/${id}`)
.pipe(
catchError(this.handleError)
);
}

// Error handling method
private handleError(error: any) {
let errorMessage = '';
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}`;
}
console.error(errorMessage);
return throwError(() => errorMessage);
}

Using Query Parameters

To add query parameters to your GET request, you can use the HttpParams class:

typescript
// Get books by genre
getBooksByGenre(genre: string): Observable<Book[]> {
const params = new HttpParams().set('genre', genre);

return this.http.get<Book[]>(this.apiUrl, { params })
.pipe(
catchError(this.handleError)
);
}

Making HTTP POST Requests

POST requests are used to send data to a server to create a resource:

typescript
// Add a new book
addBook(book: Book): Observable<Book> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};

return this.http.post<Book>(this.apiUrl, book, httpOptions)
.pipe(
catchError(this.handleError)
);
}

Making HTTP PUT Requests

PUT requests are used to update existing resources:

typescript
// Update an existing book
updateBook(book: Book): Observable<Book> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json'
})
};

return this.http.put<Book>(`${this.apiUrl}/${book.id}`, book, httpOptions)
.pipe(
catchError(this.handleError)
);
}

Making HTTP DELETE Requests

DELETE requests are used to remove resources:

typescript
// Delete a book
deleteBook(id: number): Observable<any> {
return this.http.delete(`${this.apiUrl}/${id}`)
.pipe(
catchError(this.handleError)
);
}

Setting HTTP Headers

Headers can be used to provide additional information about the request or the client:

typescript
// Get books with custom headers
getBooksWithHeaders(): Observable<Book[]> {
const httpOptions = {
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': 'Bearer yourTokenHere',
'X-Custom-Header': 'CustomValue'
})
};

return this.http.get<Book[]>(this.apiUrl, httpOptions)
.pipe(
catchError(this.handleError)
);
}

Consuming HTTP Services in Components

Now let's see how to use our service in a component:

typescript
// book-list.component.ts
import { Component, OnInit } from '@angular/core';
import { Book } from '../book.model';
import { BookService } from '../book.service';

@Component({
selector: 'app-book-list',
templateUrl: './book-list.component.html',
styleUrls: ['./book-list.component.css']
})
export class BookListComponent implements OnInit {
books: Book[] = [];
errorMessage: string = '';
loading: boolean = true;

constructor(private bookService: BookService) { }

ngOnInit(): void {
this.getBooks();
}

getBooks(): void {
this.bookService.getBooks()
.subscribe({
next: (books) => {
this.books = books;
this.loading = false;
},
error: (error) => {
this.errorMessage = error;
this.loading = false;
}
});
}

addBook(book: Book): void {
this.bookService.addBook(book)
.subscribe({
next: (newBook) => {
this.books.push(newBook);
},
error: (error) => {
this.errorMessage = error;
}
});
}

deleteBook(id: number): void {
this.bookService.deleteBook(id)
.subscribe({
next: () => {
this.books = this.books.filter(book => book.id !== id);
},
error: (error) => {
this.errorMessage = error;
}
});
}
}

And the corresponding template:

html
<!-- book-list.component.html -->
<h2>Book List</h2>

<div *ngIf="loading">Loading books...</div>
<div *ngIf="errorMessage" class="error">{{ errorMessage }}</div>

<ul *ngIf="books.length > 0">
<li *ngFor="let book of books">
{{ book.title }} by {{ book.author }} ({{ book.year }})
<button (click)="deleteBook(book.id)">Delete</button>
</li>
</ul>

<div *ngIf="!loading && books.length === 0">
No books available.
</div>

Real-world Example: Working with a RESTful API

Let's create an example that uses the popular JSONPlaceholder API, a free online REST API for testing:

typescript
// post.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';

export interface Post {
userId: number;
id?: number;
title: string;
body: string;
}

@Injectable({
providedIn: 'root'
})
export class PostService {
private apiUrl = 'https://jsonplaceholder.typicode.com/posts';

constructor(private http: HttpClient) { }

getPosts(): Observable<Post[]> {
return this.http.get<Post[]>(this.apiUrl);
}

createPost(post: Post): Observable<Post> {
return this.http.post<Post>(this.apiUrl, post);
}

updatePost(post: Post): Observable<Post> {
return this.http.put<Post>(`${this.apiUrl}/${post.id}`, post);
}

deletePost(id: number): Observable<any> {
return this.http.delete(`${this.apiUrl}/${id}`);
}
}

Now we can use this service in a component:

typescript
// post-manager.component.ts
import { Component, OnInit } from '@angular/core';
import { Post, PostService } from '../post.service';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';

@Component({
selector: 'app-post-manager',
templateUrl: './post-manager.component.html',
styleUrls: ['./post-manager.component.css']
})
export class PostManagerComponent implements OnInit {
posts: Post[] = [];
postForm: FormGroup;
isEditing = false;
currentPostId: number | null = null;

constructor(
private postService: PostService,
private fb: FormBuilder
) {
this.postForm = this.fb.group({
title: ['', Validators.required],
body: ['', Validators.required],
userId: [1, Validators.required]
});
}

ngOnInit(): void {
this.loadPosts();
}

loadPosts(): void {
this.postService.getPosts()
.subscribe(posts => {
this.posts = posts.slice(0, 10); // Just get first 10 for demo purposes
});
}

onSubmit(): void {
if (this.postForm.invalid) {
return;
}

const post: Post = this.postForm.value;

if (this.isEditing && this.currentPostId) {
post.id = this.currentPostId;
this.postService.updatePost(post)
.subscribe(updatedPost => {
const index = this.posts.findIndex(p => p.id === this.currentPostId);
if (index !== -1) {
this.posts[index] = updatedPost;
}
this.resetForm();
});
} else {
this.postService.createPost(post)
.subscribe(newPost => {
this.posts.unshift(newPost);
this.resetForm();
});
}
}

editPost(post: Post): void {
this.isEditing = true;
this.currentPostId = post.id;
this.postForm.setValue({
title: post.title,
body: post.body,
userId: post.userId
});
}

deletePost(id: number): void {
this.postService.deletePost(id)
.subscribe(() => {
this.posts = this.posts.filter(post => post.id !== id);
});
}

resetForm(): void {
this.isEditing = false;
this.currentPostId = null;
this.postForm.reset({
title: '',
body: '',
userId: 1
});
}
}

And the corresponding template:

html
<!-- post-manager.component.html -->
<div class="post-manager">
<h2>{{ isEditing ? 'Edit Post' : 'Create Post' }}</h2>

<form [formGroup]="postForm" (ngSubmit)="onSubmit()">
<div class="form-group">
<label for="title">Title</label>
<input type="text" id="title" formControlName="title">
<div *ngIf="postForm.get('title')?.invalid && postForm.get('title')?.touched" class="error">
Title is required
</div>
</div>

<div class="form-group">
<label for="body">Body</label>
<textarea id="body" formControlName="body" rows="4"></textarea>
<div *ngIf="postForm.get('body')?.invalid && postForm.get('body')?.touched" class="error">
Body is required
</div>
</div>

<div class="form-actions">
<button type="submit" [disabled]="postForm.invalid">
{{ isEditing ? 'Update' : 'Create' }}
</button>
<button type="button" *ngIf="isEditing" (click)="resetForm()">Cancel</button>
</div>
</form>

<h2>Posts</h2>
<div *ngIf="posts.length === 0" class="no-data">No posts available</div>

<div class="post-list">
<div *ngFor="let post of posts" class="post-item">
<h3>{{ post.title }}</h3>
<p>{{ post.body }}</p>
<div class="post-actions">
<button (click)="editPost(post)">Edit</button>
<button (click)="deletePost(post.id!)">Delete</button>
</div>
</div>
</div>
</div>

Handling HTTP Response Events

Sometimes you need more control over the HTTP response. Angular's HttpClient allows you to listen for progress events:

typescript
// Download a large file with progress
downloadFile(fileId: string): Observable<any> {
return this.http.get(`${this.apiUrl}/files/${fileId}`, {
reportProgress: true,
observe: 'events',
responseType: 'blob'
}).pipe(
tap(event => {
if (event.type === HttpEventType.DownloadProgress) {
// Calculate download progress
const percentDone = Math.round(100 * event.loaded / (event.total || 1));
console.log(`Download progress: ${percentDone}%`);
} else if (event.type === HttpEventType.Response) {
console.log('Download complete');
}
}),
catchError(this.handleError)
);
}

Note: For the above example to work, you need to import HttpEventType and tap operator:

typescript
import { HttpEventType } from '@angular/common/http';
import { catchError, retry, tap } from 'rxjs/operators';

HTTP Interceptors

HTTP interceptors are a powerful feature in Angular that allow you to intercept and modify HTTP requests and responses. Common use cases include:

  • Adding authentication tokens to requests
  • Logging requests and responses
  • Error handling
  • Loading indicators

Here's an example of an authentication interceptor:

typescript
// auth.interceptor.ts
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor
} from '@angular/common/http';
import { Observable } from 'rxjs';
import { AuthService } from './auth.service';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

constructor(private authService: AuthService) {}

intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
// Get the auth token from the service
const authToken = this.authService.getAuthToken();

// Clone the request and add the authorization header
const authReq = request.clone({
headers: request.headers.set('Authorization', `Bearer ${authToken}`)
});

// Send the cloned request instead of the original request
return next.handle(authReq);
}
}

To register the interceptor, add it to the providers in your app module:

typescript
// app.module.ts
import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({
// ...
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
],
// ...
})
export class AppModule { }

Summary

In this tutorial, we've covered the essential aspects of making HTTP requests in Angular:

  • Setting up the HttpClient module
  • Creating services for HTTP operations
  • Making GET, POST, PUT, and DELETE requests
  • Handling responses and errors
  • Working with headers, query parameters, and response events
  • Implementing HTTP interceptors

By following these patterns, you can create robust and maintainable Angular applications that communicate effectively with backend services.

Additional Resources

Exercises

  1. Create a service that communicates with a public API like PokeAPI or The Star Wars API.
  2. Implement a loading interceptor that shows a loading indicator whenever there's an active HTTP request.
  3. Create a caching service that stores HTTP responses and serves them from cache when possible.
  4. Build a retry mechanism that retries failed HTTP requests with incremental backoff.
  5. Implement error handling that displays appropriate messages to users based on different types of HTTP errors.

Understanding HTTP requests is fundamental for any Angular application that needs to communicate with backend services. With the knowledge from this guide, you're well-equipped to create applications that efficiently fetch, create, update, and delete data from your APIs.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)