Skip to main content

Overview

ScreenPulse uses a centralized error handling approach with Angular’s HTTP interceptors and ngx-toastr for user-friendly error notifications. This guide covers the error handling patterns used throughout the application.

Error Interceptor

Implementation

The ErrorInterceptor is the core component for handling HTTP errors globally. It intercepts all HTTP responses and transforms errors into user-friendly messages.
import { Injectable } from '@angular/core';
import {
  HttpEvent,
  HttpInterceptor,
  HttpHandler,
  HttpRequest,
  HttpErrorResponse
} from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
    return next.handle(req).pipe(
      catchError((error: HttpErrorResponse) => {
        let message = 'An unknown error occurred.';
        switch (error.status) {
          case 0:
            message = 'Cannot connect to server. Please check your internet connection';
            break;
          case 401:
            message = 'Unauthorized access. Please try it again';
             if (error.error.code === 'AUTH_TOKEN_EXPIRED') {
              message = 'Session expired. Please login again';
            }
            break;
          case 404:
            message = 'The favorite you are trying to delete could not be found in your list. Please try again later.';
            break;
          case 409:
            if (error.error.code === 'USER_EXISTS') {
              message = 'User with this email already exists';
            }
            if (error.error.code === 'FAVORITE_EXISTS') {
              message = 'Media item already in favorites';
            }
            break;
          default:
            message = 'An unexpected error occurred. Please try again later';
            break;
        }

        return throwError(() => ({
          message,
          status: error.status,
        }));
      })
    );
  }
}

Error Status Codes

The interceptor handles different HTTP status codes:
Scenario: Server is unreachable or network connectivity issuesMessage: “Cannot connect to server. Please check your internet connection”Use Case: When the user is offline or the backend server is down
Scenario: Authentication required or token expiredMessages:
  • Default: “Unauthorized access. Please try it again”
  • Token expired: “Session expired. Please login again”
Use Case: When accessing protected routes without valid authentication
Scenario: Resource not foundMessage: “The favorite you are trying to delete could not be found in your list. Please try again later.”Use Case: Attempting to delete a favorite that doesn’t exist
Scenario: Resource conflictMessages:
  • USER_EXISTS: “User with this email already exists”
  • FAVORITE_EXISTS: “Media item already in favorites”
Use Case: Duplicate user registration or adding duplicate favorites

Toast Notifications

Configuration

ScreenPulse uses ngx-toastr for displaying error messages to users. The library is imported globally:
styles.scss
@import 'ngx-toastr/toastr';
Toast notifications provide non-intrusive feedback to users without blocking the UI, making error handling more user-friendly.

Usage in Services

Services catch errors from the interceptor and display toast notifications:
deleteMediaItem(mediaId: string): Observable<DeleteResponse> {
  const token = this.authService.getAuthToken();
  const options = {
    headers: new HttpHeaders({
      'Authorization': `Bearer ${token}`
    })
  };
  return this.http.delete<DeleteResponse>(`${this.baseUrl}/${mediaId}`, options);
}

RxJS Error Handling Patterns

catchError Operator

The catchError operator is used throughout services to handle errors gracefully:
1

Import the operator

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

Apply in pipe

return this.http.get(url).pipe(
  catchError((error: HttpErrorResponse) => {
    // Transform error
    return throwError(() => ({ message: 'Custom message', status: error.status }));
  })
);
3

Handle in component

this.service.getData().subscribe({
  next: (data) => this.handleData(data),
  error: (error) => this.toastr.error(error.message)
});

Service Error Handling Example

Here’s how the OmdbService handles errors:
omdb.service.ts
fetchMediaItems(title: string, type: string, year: string, page: number): Observable<OmdbResponse> {
  const options = {
    params: new HttpParams()
      .set('title', title.trim())
      .set('type', type)
      .set('year', year)
      .set('page', page.toString())
  };
  return this.http.get<OmdbResponse>(`${environment.serverSearchURL}`, options)
  // Error handling is done by ErrorInterceptor
}
The ErrorInterceptor automatically catches all HTTP errors, so individual services don’t need to implement catchError unless they need custom error handling logic.

Best Practices

1. Centralized Error Messages

Keep all error messages in the interceptor for consistency:
// ✅ Good - Centralized in interceptor
case 409:
  if (error.error.code === 'FAVORITE_EXISTS') {
    message = 'Media item already in favorites';
  }
  break;

// ❌ Bad - Scattered across components
this.toastr.error('Media item already in favorites');

2. Use Error Codes

Leverage backend error codes for specific messages:
if (error.error.code === 'AUTH_TOKEN_EXPIRED') {
  message = 'Session expired. Please login again';
}

3. Graceful Degradation

Always provide a default error message:
default:
  message = 'An unexpected error occurred. Please try again later';
  break;

4. Error Response Structure

Return consistent error objects:
return throwError(() => ({
  message,     // User-friendly message
  status: error.status  // HTTP status code
}));

Complete Example

Here’s a complete example of error handling in the FavoritesService:
favorites.service.ts
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { AuthService } from 'src/app/core/services/auth.service';

addToFavorites(movie: MediaItem): Observable<MediaItem> {
  const token = this.authService.getAuthToken();
  const options = {
    headers: new HttpHeaders({
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${token}`
    }),
  };
  return this.http.post<MediaItem>(this.baseUrl, movie, options)
  // ErrorInterceptor will catch:
  // - 401: Token expired
  // - 409: Duplicate favorite (FAVORITE_EXISTS)
  // - 0: Network error
}
component.ts
// Component usage
addFavorite(movie: MediaItem) {
  this.favoritesService.addToFavorites(movie).subscribe({
    next: (response) => {
      this.toastr.success('Added to favorites!');
    },
    error: (error) => {
      // Error.message comes from ErrorInterceptor
      this.toastr.error(error.message);
    }
  });
}

Authentication

Learn about handling auth-related errors

API Integration

Understand HTTP service patterns

Build docs developers (and LLMs) love