Part 9: HTTP Client – Communicating with Backend APIs

Welcome to our deep dive into Angular’s HttpClient! This powerful module enables seamless communication with backend services and APIs. Let’s master it together.

Why HttpClient?

Angular’s HttpClient provides:

  • A testable, type-safe way to make HTTP requests
  • Observable-based API
  • Interceptor support
  • Request/response transformation
  • Error handling built-in

Setting Up HttpClient

First, import HttpClientModule in your root module:

typescript

// app.module.ts
import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule  // ← Add this
  ]
})
export class AppModule { }

Basic HTTP Operations

Here’s how to perform CRUD operations:

1. GET Request (Fetch Data)

typescript

// data.service.ts
getPosts(): Observable<Post[]> {
  return this.http.get<Post[]>('https://api.example.com/posts');
}

2. POST Request (Create Data)

typescript

createPost(post: Post): Observable<Post> {
  return this.http.post<Post>('https://api.example.com/posts', post);
}

3. PUT/PATCH Request (Update Data)

typescript

updatePost(id: number, post: Post): Observable<Post> {
  return this.http.put<Post>(`https://api.example.com/posts/${id}`, post);
  
  // Or for partial updates:
  // return this.http.patch<Post>(...)
}

4. DELETE Request (Remove Data)

typescript

deletePost(id: number): Observable<void> {
  return this.http.delete<void>(`https://api.example.com/posts/${id}`);
}

Advanced Request Configuration

Customize requests with options:

typescript

getPosts(): Observable<Post[]> {
  return this.http.get<Post[]>('https://api.example.com/posts', {
    params: new HttpParams().set('limit', '10'),  // Query params
    headers: new HttpHeaders().set('Authorization', 'Bearer token'),
    observe: 'response'  // Get full HttpResponse
  });
}

Handling Responses

Process responses properly:

typescript

getPosts(): Observable<Post[]> {
  return this.http.get<Post[]>('/api/posts').pipe(
    tap(response => console.log('Raw response:', response)),
    map(response => response.map(post => ({
      ...post,
      createdAt: new Date(post.createdAt)
    }))),
    catchError(this.handleError)
  );
}

private handleError(error: HttpErrorResponse) {
  if (error.status === 0) {
    console.error('Client-side or network error:', error.error);
  } else {
    console.error(`Backend returned code ${error.status}:`, error.error);
  }
  return throwError(() => new Error('Something went wrong'));
}

Interceptors: Powerful Middleware

Create interceptors for cross-cutting concerns:

typescript

// auth.interceptor.ts
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authToken = localStorage.getItem('auth_token');
    
    if (authToken) {
      const authReq = req.clone({
        headers: req.headers.set('Authorization', `Bearer ${authToken}`)
      });
      return next.handle(authReq);
    }
    
    return next.handle(req);
  }
}

Register interceptors in your module:

typescript

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
]

Common Interceptor Use Cases

  1. Authentication: Add auth tokens
  2. Error Handling: Global error processing
  3. Caching: Cache responses
  4. Logging: Track requests
  5. Mocking: Fake backend for development

Progress Events

Track upload/download progress:

typescript

uploadFile(file: File): Observable<HttpEvent<any>> {
  const formData = new FormData();
  formData.append('file', file);

  return this.http.post('/api/upload', formData, {
    reportProgress: true,
    observe: 'events'
  }).pipe(
    filter(event => event.type === HttpEventType.UploadProgress),
    map(event => {
      if (event.type === HttpEventType.UploadProgress) {
        return Math.round(100 * event.loaded / (event.total || 1));
      }
      return 0;
    })
  );
}

Best Practices

  1. Service Layer: Keep HTTP calls in services, not components
  2. Type Safety: Always type your requests/responses
  3. Error Handling: Handle errors at service level
  4. Unsubscribe: Manage subscriptions properly
  5. Environment Config: Store API URLs in environment files

Hands-On Exercise

Build a complete UserService with:

  1. Methods for all CRUD operations
  2. JWT authentication interceptor
  3. Error handling
  4. Type-safe interfaces for User model

What’s Next?

In Part 10, we’ll explore Routing & Navigation – how to build single-page application navigation!

Leave a Comment

Your email address will not be published. Required fields are marked *