Angular’s HttpClient provides a typed, Observable-based API for communicating with REST backends.

Setup

Register HttpClient in your app config:

  import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withFetch } from '@angular/common/http';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withFetch())  // Uses fetch API under the hood
  ]
};
  

GET Request

  import { Component, inject, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { CommonModule } from '@angular/common';

interface Post {
  id: number;
  title: string;
  body: string;
}

@Component({
  selector: 'app-post-list',
  standalone: true,
  imports: [CommonModule],
  template: `
    @if (loading) { <p>Loading...</p> }
    @for (post of posts; track post.id) {
      <h2>{{ post.title }}</h2>
    }
  `
})
export class PostListComponent implements OnInit {
  private http = inject(HttpClient);

  posts: Post[] = [];
  loading = false;
  error = '';

  ngOnInit() {
    this.loadPosts();
  }

  loadPosts() {
    this.loading = true;
    this.http.get<Post[]>('https://jsonplaceholder.typicode.com/posts')
      .subscribe({
        next: data => {
          this.posts = data.slice(0, 5);
          this.loading = false;
        },
        error: err => {
          this.error = 'Failed to load posts';
          this.loading = false;
          console.error(err);
        }
      });
  }
}
  

POST, PUT, DELETE

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

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

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

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

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

Request Options

Pass headers, query params, and observe response details:

  import { HttpParams, HttpHeaders } from '@angular/common/http';

const params = new HttpParams().set('page', '1').set('limit', '10');
const headers = new HttpHeaders({ Authorization: 'Bearer my-token' });

this.http.get<Post[]>('/api/posts', { params, headers });
  

Error Handling with catchError

Centralize error logic in a service:

  import { catchError, throwError } from 'rxjs';

getPosts() {
  return this.http.get<Post[]>(this.apiUrl).pipe(
    catchError(err => {
      console.error('API error:', err);
      return throwError(() => new Error('Something went wrong'));
    })
  );
}
  

HTTP Interceptors

Attach auth tokens to every request:

  export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const token = localStorage.getItem('token');
  return next(token
    ? req.clone({ setHeaders: { Authorization: `Bearer ${token}` } })
    : req);
};

// app.config.ts
provideHttpClient(withInterceptors([authInterceptor]))
  

Keep HTTP logic in services, not components — components subscribe and update the UI.