Back to all posts

HTTP request and handling response in Angular


HTTP requests from the backbone of modern web application, allowing them to communicate with servers and fetch or send data.

Angular provide a powerful HTTP client that makes working with HTTP request straightforward and efficient.

Setting Up HTTP Client

Before making HTTP requests, we need to import the HttpClientModule in out application’s root module:

import { HttpClientModule } from '@angular/common/http';

@NgModule({
  imports: [
    HttpClientModule
  ],
  // ... other module configurations
})
export class AppModule { }

Creating a Service

It’s a best practice to encapsulate HTTP requests within services. Here’s an example of a service that handles user-related HTTP requests:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http';
import { Observable, catchError, throwError } from 'rxjs';

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

  constructor(private http: HttpClient) { }

  // GET request
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>(this.apiUrl)
      .pipe(
        catchError(this.handleError)
      );
  }

  // POST request
  createUser(user: User): Observable<User> {
    const headers = new HttpHeaders().set('Content-Type', 'application/json');
    
    return this.http.post<User>(this.apiUrl, user, { headers })
      .pipe(
        catchError(this.handleError)
      );
  }

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

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

  // Error handling
  private handleError(error: any) {
    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}\nMessage: ${error.message}`;
    }
    return throwError(() => errorMessage);
  }
}

Using HTTP Parameters and Headers

Sometimes we need to add query parameters or custom headers to our requests:

// Adding query parameters
getFilteredUsers(age: number, city: string): Observable<User[]> {
  const params = new HttpParams()
    .set('age', age.toString())
    .set('city', city);

  return this.http.get<User[]>(this.apiUrl, { params })
    .pipe(
      catchError(this.handleError)
    );
}

// Adding custom headers
getProtectedResource(): Observable<any> {
  const headers = new HttpHeaders()
    .set('Authorization', 'Bearer ' + this.authToken)
    .set('Custom-Header', 'custom-value');

  return this.http.get(this.apiUrl, { headers })
    .pipe(
      catchError(this.handleError)
    );
}

Using the Service in Components

Here’s how to use the service in a component:

@Component({
  selector: 'app-user-list',
  template: `
    <div *ngIf="users$ | async as users">
      <div *ngFor="let user of users">
        {{ user.name }}
      </div>
    </div>
  `
})
export class UserListComponent implements OnInit {
  users$: Observable<User[]>;

  constructor(private userService: UserService) {
    this.users$ = this.userService.getUsers();
  }

  addUser(user: User) {
    this.userService.createUser(user).subscribe({
      next: (response) => {
        console.log('User created successfully', response);
        // Handle success
      },
      error: (error) => {
        console.error('Error creating user', error);
        // Handle error
      }
    });
  }
}

Handling Different Response Types

Angular’s HTTP client supports various response types:

// Get response as JSON (default)
getData(): Observable<any> {
  return this.http.get(this.apiUrl);
}

// Get full response including headers
getData(): Observable<HttpResponse<any>> {
  return this.http.get(this.apiUrl, { observe: 'response' });
}

// Get response as text
getText(): Observable<string> {
  return this.http.get(this.apiUrl, { responseType: 'text' });
}

// Get response as blob (for files)
getFile(): Observable<Blob> {
  return this.http.get(this.apiUrl, { responseType: 'blob' });
}

Implementing Interceptors

HTTP interceptors allow you to modify requests or responses globally:

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(
    request: HttpRequest<any>,
    next: HttpHandler
  ): Observable<HttpEvent<any>> {
    // Clone and modify the request
    const authReq = request.clone({
      headers: request.headers.set('Authorization', 'Bearer ' + this.getToken())
    });

    return next.handle(authReq);
  }

  private getToken(): string {
    // Get token from storage
    return localStorage.getItem('token') || '';
  }
}

Register the interceptor in your module:

Using NgModule Providers

import { HTTP_INTERCEPTORS } from '@angular/common/http';
import { AuthInterceptor } from './auth.interceptor';

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

Using providedIn Root with Factory Function

import { HttpInterceptorFn, provideHttpClient, withInterceptors } from '@angular/common/http';

// Define interceptor as a function
export const authInterceptor: HttpInterceptorFn = (req, next) => {
  const authReq = req.clone({
    headers: req.headers.set('Authorization', `Bearer ${getToken()}`)
  });
  return next(authReq);
};

// In your app.config.ts or similar
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([authInterceptor])
    )
  ]
};

Best Practices

  1. Error Handling: Always implement proper error handling using catchError operator.
  2. Type Safety: Use interfaces to define the shape of your response data.
  3. Cancellation: Use takeUntil operator to cancel ongoing requests when components are destroyed.
  4. Loading States: Implement loading indicators for better user experience.
  5. Retry Logic: Use retry operator for transient failures.