Lesson 09-Angular HTTP Client

HTTP Client Features

Angular’s HTTP client is a core tool for communicating with backend services. It provides methods to send HTTP requests and receive responses, including GET, POST, PUT, and DELETE. Built on RxJS, Angular’s HTTP client simplifies handling asynchronous data streams.

Importing HttpClientModule

To use the HTTP client, import HttpClientModule in AppModule by adding it to the @NgModule decorator’s imports array.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

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

Using HttpClient

The HttpClient class provides methods for sending HTTP requests:

  • get(): Sends a GET request.
  • post(): Sends a POST request.
  • put(): Sends a PUT request.
  • delete(): Sends a DELETE request.

These methods return an Observable, which you can subscribe to for handling response data.

Creating an HTTP Service

Encapsulate backend communication in a dedicated HTTP service. Here’s an example:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  getData(): Observable<any> {
    return this.http.get(`${this.apiUrl}/data`);
  }

  postData(data: any): Observable<any> {
    return this.http.post(`${this.apiUrl}/data`, data);
  }
}

Handling Responses

Inject the HTTP service into a component and subscribe to its methods to process response data:

import { Component } from '@angular/core';
import { HttpService } from './http.service';

@Component({
  selector: 'app-root',
  template: `
    <h1>Data</h1>
    <ul>
      <li *ngFor="let item of items">{{ item.name }}</li>
    </ul>
  `,
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  items: any[];

  constructor(private httpService: HttpService) {
    this.httpService.getData().subscribe(
      response => {
        this.items = response;
      },
      error => {
        console.error('Error fetching data:', error);
      }
    );
  }
}

Error Handling

Errors are typically handled in the error callback of a subscription. Use try/catch or RxJS’s catchError operator:

import { catchError } from 'rxjs/operators';
import { throwError } from 'rxjs';

getData(): Observable<any> {
  return this.http.get(`${this.apiUrl}/data`).pipe(
    catchError(error => {
      console.error('Error fetching data:', error);
      return throwError('Something went wrong; please try again later.');
    })
  );
}

Custom HTTP Headers

Customize HTTP request headers, such as adding authentication tokens:

postData(data: any): Observable<any> {
  const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer token' };
  return this.http.post(`${this.apiUrl}/data`, data, { headers });
}

Using Interceptors

Interceptors allow intercepting and modifying HTTP requests and responses, useful for global error handling, logging, or header modifications:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authReq = req.clone({ headers: req.headers.set('Authorization', 'Bearer token') });
    return next.handle(authReq);
  }
}

Register the interceptor in AppModule:

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

Summary

Angular’s HTTP client offers a robust and flexible API for backend communication. Using HttpClient, you can easily send various HTTP requests and handle responses via Observables. Creating dedicated HTTP services, handling errors, customizing headers, and using interceptors help build robust and maintainable Angular applications.

Setting Up HttpClient

Angular’s HttpClient is a powerful tool for handling HTTP requests and responses with backend servers. This section guides you through setting up and using HttpClient, covering basic configuration, request types, error handling, custom headers, interceptors, and advanced topics.

Importing HttpClientModule

Import HttpClientModule in AppModule’s imports array:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

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

Creating an HTTP Service

Create a service to encapsulate HTTP requests, keeping components clean and enabling reuse:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  getItems(): Observable<any[]> {
    return this.http.get<any[]>(`${this.baseUrl}/items`);
  }

  createItem(item: any): Observable<any> {
    return this.http.post(`${this.baseUrl}/items`, item);
  }

  updateItem(id: number, item: any): Observable<any> {
    return this.http.put(`${this.baseUrl}/items/${id}`, item);
  }

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

Using the HTTP Service in Components

Inject the service into a component to send HTTP requests:

import { Component, OnInit } from '@angular/core';
import { ApiService } from './api.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  items: any[] = [];

  constructor(private apiService: ApiService) { }

  ngOnInit() {
    this.getItems();
  }

  getItems() {
    this.apiService.getItems().subscribe(
      items => this.items = items,
      error => console.error('Error fetching items:', error)
    );
  }

  createItem() {
    const newItem = { /* item data */ };
    this.apiService.createItem(newItem).subscribe(
      () => this.getItems(),
      error => console.error('Error creating item:', error)
    );
  }
}

Error Handling

Handle errors in the subscribe method’s error callback:

apiService.getItems().subscribe(
  items => this.items = items,
  error => {
    console.error('Error fetching items:', error);
    alert('Failed to fetch items.');
  }
);

Custom Request Headers

Pass an options object to customize headers:

updateItem(id: number, item: any): Observable<any> {
  const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer your-token' };
  return this.http.put(`${this.baseUrl}/items/${id}`, item, { headers });
}

Using Interceptors

Interceptors modify HTTP requests and responses, useful for global error handling or header modifications:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authRequest = request.clone({ headers: request.headers.set('Authorization', 'Bearer your-token') });
    return next.handle(authRequest);
  }
}

Register in AppModule:

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

Using Forms and FormData

Use FormData for file uploads or form data:

uploadFile(file: File): Observable<any> {
  const formData = new FormData();
  formData.append('file', file);
  return this.http.post(`${this.baseUrl}/upload`, formData);
}

Using JSONP

For APIs not supporting CORS, use JSONP:

fetchJsonp(url: string): Observable<any> {
  return this.http.jsonp(url, 'callback');
}

Performance Optimization

  • HTTP Caching: Cache frequently accessed data to reduce network requests.
  • Lazy Loading: Use lazy loading and dynamic imports to reduce initial load time.

Testing

Write unit tests to ensure HTTP services work as expected:

it('should fetch items', () => {
  const items = [{ id: 1, name: 'Item 1' }];
  service.getItems().subscribe(data => expect(data).toEqual(items));
  expect(httpSpy.get).toHaveBeenCalledWith('https://api.example.com/items');
});

Making Requests

Angular’s HttpClient module is a robust tool for handling HTTP requests, built on RxJS for reactive server communication.

Importing HttpClientModule

Ensure HttpClientModule is imported in AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';

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

Creating an HTTP Service

Encapsulate HTTP requests in a dedicated service:

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  // GET request
  getItems(): Observable<any[]> {
    return this.http.get<any[]>(`${this.apiUrl}/items`);
  }

  // POST request
  createItem(item: any): Observable<any> {
    return this.http.post(`${this.apiUrl}/items`, item);
  }

  // PUT request
  updateItem(id: number, item: any): Observable<any> {
    return this.http.put(`${this.apiUrl}/items/${id}`, item);
  }

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

Making a GET Request

GET requests retrieve data from the server:

import { Component, OnInit } from '@angular/core';
import { ApiService } from './api.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
  items: any[];

  constructor(private apiService: ApiService) { }

  ngOnInit() {
    this.getItems();
  }

  getItems() {
    this.apiService.getItems().subscribe(
      items => this.items = items,
      error => console.error('Error fetching items:', error)
    );
  }
}

Making a POST Request

POST requests send data to the server:

createItem() {
  const newItem = { /* item data */ };
  this.apiService.createItem(newItem).subscribe(
    () => this.getItems(),
    error => console.error('Error creating item:', error)
  );
}

Making a PUT Request

PUT requests update resources on the server:

updateItem(id: number) {
  const updatedItem = { /* updated item data */ };
  this.apiService.updateItem(id, updatedItem).subscribe(
    () => this.getItems(),
    error => console.error('Error updating item:', error)
  );
}

Making a DELETE Request

DELETE requests remove resources from the server:

deleteItem(id: number) {
  this.apiService.deleteItem(id).subscribe(
    () => this.getItems(),
    error => console.error('Error deleting item:', error)
  );
}

Error Handling

Prepare for potential errors during requests:

apiService.getItems().subscribe(
  items => this.items = items,
  error => {
    console.error('Error fetching items:', error);
    alert('Failed to fetch items.');
  }
);

Custom Request Headers

Include specific headers, such as authentication tokens:

updateItem(id: number, item: any): Observable<any> {
  const headers = { 'Content-Type': 'application/json', 'Authorization': 'Bearer your-token' };
  return this.http.put(`${this.apiUrl}/items/${id}`, item, { headers });
}

Uploading Files with FormData

Use FormData for file uploads:

uploadFile(file: File): Observable<any> {
  const formData = new FormData();
  formData.append('file', file);
  return this.http.post(`${this.apiUrl}/upload`, formData);
}

Using HTTP Interceptors

Interceptors modify requests or responses, such as adding global headers or handling errors:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TokenInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authRequest = request.clone({ headers: request.headers.set('Authorization', 'Bearer your-token') });
    return next.handle(authRequest);
  }
}

Register in AppModule:

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

Using JSONP

For APIs without CORS support, use JSONP:

fetchJsonp(url: string): Observable<any> {
  return this.http.jsonp(url, 'callback');
}

Performance Optimization

  • HTTP Caching: Cache frequently accessed data to reduce network requests.
  • Lazy Loading: Use lazy loading and dynamic imports to minimize initial load time.

Testing

Write unit tests to verify HTTP services:

it('should fetch items', () => {
  const items = [{ id: 1, name: 'Item 1' }];
  service.getItems().subscribe(data => expect(data).toEqual(items));
  expect(httpSpy.get).toHaveBeenCalledWith('https://api.example.com/items');
});

Testing Response Interception

HTTP interceptors in Angular are powerful tools for executing operations before requests are sent to the server or after responses are received. For example, interceptors can add authentication tokens, modify headers, handle errors, or cancel requests. Properly testing interceptors is crucial for ensuring application stability and security.

Creating a Simple HTTP Interceptor

Create an interceptor that adds a global authentication token to every request:

import { Injectable } from '@angular/core';
import { HttpEvent, HttpInterceptor, HttpHandler, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const authRequest = request.clone({ headers: request.headers.set('Authorization', 'Bearer your-token') });
    return next.handle(authRequest);
  }
}

Registering the Interceptor

Register the interceptor in AppModule or another provider module:

@NgModule({
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  declarations: [AppComponent],
  providers: [
    { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Testing the Interceptor

Use Angular’s testing tools, such as TestBed and HttpClientTestingModule, to test the interceptor:

import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { HttpClient } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

describe('AuthInterceptor', () => {
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true }
      ]
    });

    httpMock = TestBed.inject(HttpTestingController);
  });

  afterEach(() => {
    httpMock.verify(); // Ensure no outstanding requests
  });

  it('should add an Authorization header to requests', () => {
    const expectedUrl = 'https://api.example.com/data';
    const testData = { message: 'Hello, World!' };

    // Send a GET request
    const http = TestBed.inject(HttpClient);
    http.get(expectedUrl).subscribe(data => {
      expect(data).toEqual(testData);
    });

    // Verify the request includes the correct Authorization header
    const req = httpMock.expectOne(expectedUrl);
    expect(req.request.headers.get('Authorization')).toEqual('Bearer your-token');

    // Simulate server response
    req.flush(testData);
  });
});

Testing Error Handling

If the interceptor handles errors, such as uniform error messaging, test this functionality:

import { throwError } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError } from 'rxjs/operators';

// Modified interceptor
export class ErrorHandlingInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(request).pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('An error occurred:', error.message);
        return throwError(error);
      })
    );
  }
}

// Add to test
it('should handle errors correctly', () => {
  const expectedUrl = 'https://api.example.com/error';
  const http = TestBed.inject(HttpClient);

  // Send a GET request
  http.get(expectedUrl).subscribe(
    () => fail('Expected an error'),
    (error: HttpErrorResponse) => {
      expect(error.status).toEqual(404);
      expect(error.error).toEqual('Not Found');
    }
  );

  // Verify request
  const req = httpMock.expectOne(expectedUrl);
  req.flush('Not Found', { status: 404, statusText: 'Not Found' });
});

Testing Request Cancellation

If the interceptor cancels requests, test this behavior:

// Modified interceptor
export class CancelInterceptor implements HttpInterceptor {
  intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    if (request.url === 'https://api.example.com/cancel') {
      return new Observable(subscriber => subscriber.error(new Error('Request was cancelled')));
    }
    return next.handle(request);
  }
}

// Add to test
it('should cancel requests', () => {
  const expectedUrl = 'https://api.example.com/cancel';
  const http = TestBed.inject(HttpClient);

  // Send a GET request
  http.get(expectedUrl).subscribe(
    () => fail('Expected the request to be cancelled'),
    error => {
      expect(error.message).toEqual('Request was cancelled');
    }
  );

  // No need to verify request, as it should be cancelled
});
Share your love