Back to Curriculum

HTTP Services and Observables

📚 Lesson 6 of 8 ⏱️ 50 min

HTTP Services and Observables

50 min

HttpClient is Angular's service for making HTTP requests to servers. It provides a simplified API for GET, POST, PUT, DELETE, and other HTTP methods, with built-in JSON support, request/response interceptors, and error handling. HttpClient returns Observables (from RxJS) rather than Promises, enabling powerful reactive programming patterns. Understanding HttpClient is essential for building Angular applications that communicate with backend APIs.

Observables are the foundation of Angular's reactive programming model. They represent asynchronous data streams that can emit multiple values over time. Unlike Promises (which resolve once), Observables can emit multiple values and can be cancelled. Angular uses Observables extensively for HTTP requests, routing, forms, and event handling. Understanding Observables and RxJS operators enables you to handle complex asynchronous scenarios effectively.

RxJS operators transform, filter, combine, and manipulate Observable streams. Common operators include `map` (transform values), `filter` (filter values), `catchError` (handle errors), `retry` (retry failed requests), `switchMap` (switch to new Observable), `mergeMap` (merge multiple Observables), and `debounceTime` (delay emissions). Operators are chainable using the `pipe()` method, enabling powerful data transformation pipelines. Understanding RxJS operators helps you handle complex asynchronous logic elegantly.

Error handling in HTTP requests is crucial for robust applications. HttpClient Observables can emit errors, which must be handled using `catchError` operator or error callbacks in `subscribe()`. Proper error handling includes displaying user-friendly error messages, logging errors for debugging, retrying failed requests when appropriate, and providing fallback behavior. Understanding error handling helps you build applications that gracefully handle network failures and server errors.

HTTP interceptors enable you to modify requests and responses globally. Interceptors can add headers (authentication tokens), log requests, transform data, handle errors centrally, and add loading indicators. Interceptors are registered in the application's HTTP_INTERCEPTORS provider. Understanding interceptors helps you implement cross-cutting concerns like authentication, logging, and error handling consistently across your application.

Best practices include using services for HTTP calls (not components), handling errors properly with catchError, using RxJS operators for data transformation, implementing interceptors for common concerns, unsubscribing from Observables to prevent memory leaks, and using async pipe in templates when possible. Understanding HTTP services and Observables enables you to build robust, reactive Angular applications that communicate effectively with backend APIs.

Key Concepts

  • HttpClient provides HTTP functionality for Angular applications.
  • Observables handle asynchronous data streams (can emit multiple values).
  • RxJS operators transform and combine Observable streams.
  • Error handling with catchError is crucial for HTTP requests.
  • HTTP interceptors enable global request/response modification.

Learning Objectives

Master

  • Using HttpClient for HTTP requests (GET, POST, PUT, DELETE)
  • Working with Observables and RxJS operators
  • Implementing proper error handling for HTTP requests
  • Creating HTTP interceptors for cross-cutting concerns

Develop

  • Reactive programming thinking with Observables
  • Understanding asynchronous data flow
  • Designing robust API communication layers

Tips

  • Use HttpClient in services, not directly in components.
  • Handle errors with catchError operator or error callbacks.
  • Use RxJS operators (map, filter, switchMap) for data transformation.
  • Unsubscribe from Observables to prevent memory leaks.

Common Pitfalls

  • Not handling HTTP errors, causing application crashes.
  • Not unsubscribing from Observables, causing memory leaks.
  • Making HTTP calls directly in components instead of services.
  • Not using interceptors for common concerns like authentication.

Summary

  • HttpClient provides HTTP functionality with Observable-based API.
  • Observables handle asynchronous data streams and can emit multiple values.
  • RxJS operators enable powerful data transformation and combination.
  • Proper error handling is crucial for robust HTTP communication.
  • Understanding HTTP services and Observables is essential for Angular applications.

Exercise

Create HTTP services with proper error handling and observables.

// user-http.service.ts
import { Injectable } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError, retry, map } from 'rxjs/operators';

export interface User {
  id: number;
  name: string;
  email: string;
  age: number;
}

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

  constructor(private http: HttpClient) {}

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl).pipe(
      retry(3),
      catchError(this.handleError)
    );
  }

  getUserById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError)
    );
  }

  createUser(user: Omit<User, 'id'>): Observable<User> {
    return this.http.post<User>(this.apiUrl, user).pipe(
      catchError(this.handleError)
    );
  }

  updateUser(id: number, user: Partial<User>): Observable<User> {
    return this.http.put<User>(`${this.apiUrl}/${id}`, user).pipe(
      catchError(this.handleError)
    );
  }

  deleteUser(id: number): Observable<void> {
    return this.http.delete<void>(`${this.apiUrl}/${id}`).pipe(
      catchError(this.handleError)
    );
  }

  private handleError(error: HttpErrorResponse) {
    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}
Message: ${error.message}`;
    }
    console.error(errorMessage);
    return throwError(() => errorMessage);
  }
}

// user-list-http.component.ts
import { Component, OnInit } from '@angular/core';
import { UserHttpService, User } from './user-http.service';

@Component({
  selector: 'app-user-list-http',
  template: `
    <div class="user-list">
      <h2>Users from API</h2>
      <div *ngIf="loading">Loading...</div>
      <div *ngIf="error" class="error">{{ error }}</div>
      
      <div *ngFor="let user of users" class="user-item">
        <h3>{{ user.name }}</h3>
        <p>Email: {{ user.email }}</p>
        <p>Age: {{ user.age }}</p>
        <button (click)="deleteUser(user.id)">Delete</button>
      </div>
    </div>
  `,
  styles: [`
    .user-list {
      padding: 20px;
    }
    .user-item {
      border: 1px solid #ddd;
      padding: 10px;
      margin: 10px 0;
      border-radius: 5px;
    }
    .error {
      color: red;
      padding: 10px;
      background: #ffe6e6;
      border-radius: 4px;
    }
  `]
})
export class UserListHttpComponent implements OnInit {
  users: User[] = [];
  loading = false;
  error = '';

  constructor(private userService: UserHttpService) {}

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    this.loading = true;
    this.error = '';
    
    this.userService.getUsers().subscribe({
      next: (users) => {
        this.users = users;
        this.loading = false;
      },
      error: (error) => {
        this.error = error;
        this.loading = false;
      }
    });
  }

  deleteUser(id: number) {
    this.userService.deleteUser(id).subscribe({
      next: () => {
        this.users = this.users.filter(user => user.id !== id);
      },
      error: (error) => {
        this.error = error;
      }
    });
  }
}

Code Editor

Output