Skip to content

- https://chat.deepseek.com/a/chat/s/ccc6cffe-a43f-4826-9686-58e6e3f600c7

httpClientModule

A. intro

  • Asynchronously load/store data to/from backend server.
  • old : ajax, fetch/promise
  • HttpClient in @angular/common/http (simple, abstraction)
  • Additional benefits
  • typed request and response objects,
  • request/response interception
  • returns Observable --> to make async call.
  • streamlined error handling with Rxjs operator CatchError(eerr:HttpErrorResponse)->{ })

B. Developer guide

  • Add httpClientModule in rootModule
  • create service1(inject HttpClient)
  • error handling: pipe (catchError(err->{}), retry(3))
  • http.get(url, { }) or http.post/put/delete( url, data, { })
  • option object :
    • { observe: 'response/body/event' } // HttpEvent<>
    • { responseType:'json/text/blob'}
    • { param : {}, queryParam: {}, header: {} }
  • subscribe
  • Async pipe in pipeline (recommended) + auto-unsubscribe.
  • programmatically + Always unsubscribe
    import { HttpClient } from '@angular/common/http';
      // create blueprint
      - store_1$ = this.http.get("url1") : observable<T>
      - store_2$ = store_1$.pipe(map(x->x), catchError( (err:HttpErrorResponse)->{ }), retry(3), ...) 
    
      // next, consume it
      - store_2$.subscribe(data->{}, err-> {}) 
    

C Example/s

1. GET :: HttpParams, HttpHeaders, path param

//==== config.json ====
// ./assets/config.json
{
  "heroesUrl": "api/heroes",
  "textfile": "assets/textfile.txt"
}
export interface Config {  heroesUrl: string;  textfile: string; }

@Injectable()
export class ConfigService 
{
  constructor(private http: HttpClient) { } 
  apiUrl: string = url1; 
  getConfig() {  return this.http.get<Config>(assets/config.json); } //observable

  // ===== HttpParams =======
  getFilteredUsers(filter: string): Observable<User[]> {
    const params = new HttpParams().set('filter', filter);
    return this.http.get<User[]>(`${this.apiUrl}/users`, { params });
  }

  // ===== HttpHeaders =======
  createUser(user: User): Observable<User> {
    const headers = new HttpHeaders({
      'Content-Type': 'application/json',
      'X-Custom-Header': 'angular-app'
    });
    return this.http.post<User>(`${this.apiUrl}/users`, user, { headers });
  }

  // ===== path param =======
  getUserById(id: number): Observable<User> {
    return this.http.get<User>(`${this.apiUrl}/users/${id}`);
  }
}


2. GET :: HttpResponse (Full response)

  • 2nd arg: { observe: 'response' }
  • consume:
  • resp.body
  • resp.header
  • resp.header.keys()
    getConfig(): Observable<HttpResponse<Config>> {
      return this.http.get<Config>(  "url1", { observe: 'response' } );  
    }
    
    consume() {
      this.configService.getConfig()
      .subscribe(
            (resp) => { 
              this.config = { resp.body } ;   
          const keys = resp.headers.keys();
          }
            (error) => {}
        );  
    }
    

3. GET :: Progress Events (for large downloads)

  • HttpEvent
  • HttpEventType.DownloadProgress
    downloadFile(): void {
      this.http.get(`${this.apiUrl}/large-file`, {
        reportProgress: true,
        observe: 'events',
        responseType: 'blob'
      })
      .subscribe(event => {
        if (event.type === HttpEventType.DownloadProgress) {
          const percentDone = Math.round(100 * event.loaded / (event.total || 1));  //<<<
          console.log(`Download progress: ${percentDone}%`);
        } 
        else if (event instanceof HttpResponse) {
          console.log('Download complete');
          // Handle file download
        }
      });
    }
    

4. POST :: FormData (for file uploads)

uploadFile(file: File): Observable<any>
{
 // FormData, globally available in like fetch(). no impport needed.
  const formData = new FormData(); 
  formData.append('file', file);
  formData.append('description', 'File uploaded from Angular');

  return this.http.post(`${this.apiUrl}/upload`, formData, {
    reportProgress: true,
    observe: 'events'
  });
}

5. Concurrent Requests with forkJoin

loadDashboardData(): void {
  forkJoin({
    users: this.http.get<User[]>(`${this.apiUrl}/users`),
    products: this.http.get<Product[]>(`${this.apiUrl}/products`),
    stats: this.http.get<Stats>(`${this.apiUrl}/stats`)
  }).subscribe({
    next: ({ users, products, stats }) => {
      this.users = users;
      this.products = products;
      this.stats = stats;
    },
    error: (err) => console.error('Error loading dashboard data:', err)
  });
}

6. Debouncing requests


7. switchMap()


8. mergeMap()


D. Interceptors

  • Class Interceptor1/2 implements HttpInterceptor
  • override intercept(httpRequest, httpHandler)
  • injection token : HTTP_ONTERCEPTORS
  • similar to NG_VALIDATOR, Validator
    === interceptor chain ======
    -- order matters:
    providers: [
      { provide: HTTP_ONTERCEPTORS, useClass: Interceptor1, multi: true},
      { provide: HTTP_ONTERCEPTORS, useClass: Interceptor2, multi: true}
      ...
    ]
    ---
    Request → Interceptor1 → Interceptor2 → Server
    Response ← Interceptor1 ← Interceptor2 ← Server
    

1. Request Interceptor

  • Auth Interceptor
  • logging request Interceptor
  • Caching
  • global error handing
  • backend error 500
  • network issue (client error), ErrorEvent
  • Note: request is readonly/immutable 👈
newReq = req.clone({ ... }); // body not mentioned => preserve original body
newReq = req.clone({ body: undefined }); // preserve original body
newReq = req.clone({ body: null }); // clear the body
import { Injectable } from '@angular/core';
import {  HttpRequest,  HttpHandler,  HttpEvent,  HttpInterceptor,  HttpErrorResponse} from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor
{
  //========== Auth Interceptor ===========

  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> 
  {
    const token = localStorage.getItem('token');
    if (token) {
      request = request.clone({   setHeaders: {  Authorization: `Bearer ${token}` }
    });
  }

  // ========== global error handing ==========

  return next.handle(request).pipe(
      peek( x=> console.log(x)),
      //catchError((error: HttpErrorResponse) => { }),
      catchError( this.handleError ),
      retry(3)
    );
  }
  private handleError(error: HttpErrorResponse) 
  {
    // 1. A client-side or network error occurred
    if (error.error instanceof ErrorEvent) {                    
      console.error('An error occurred:', error.error.message);
    }  
    else
    {
        // 2. The backend returned an unsuccessful response code.   
        console.error(  `Backend returned code ${error.status}, ` +  `body was: ${error.error}`);
        if (error.status === 401) {
          return throwError(() => new Error('Invalid credentials'));
        } else if (error.status === 0) {
          return throwError(() => new Error('Network error'));
        } else {
          return throwError(() => new Error('Login failed'));
        }
    }
    return throwError( 'Something bad happened; please try again later.');
  }
}

2. Response Interceptor