> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/angular/angular/llms.txt
> Use this file to discover all available pages before exploring further.

# HTTP Error Handling

> Handle HTTP errors and implement retry logic in Angular applications.

## Overview

HTTP requests can fail for many reasons: network errors, server errors, client errors, or timeouts. Angular provides robust error handling mechanisms through RxJS operators and the `HttpErrorResponse` class.

## Error types

Angular distinguishes between two types of HTTP errors:

### Client-side errors

Errors that occur in the browser:

* Network connectivity issues
* DNS resolution failures
* Request cancellation
* CORS errors

```typescript theme={null}
if (error.error instanceof ErrorEvent) {
  // Client-side error
  console.error('Client error:', error.error.message);
}
```

### Server-side errors

Errors returned by the server:

* 4xx client errors (400, 401, 403, 404, etc.)
* 5xx server errors (500, 502, 503, etc.)
* Invalid responses

```typescript theme={null}
if (error.status !== 0) {
  // Server-side error
  console.error(`Server error: ${error.status} - ${error.message}`);
}
```

## HttpErrorResponse

The `HttpErrorResponse` object contains detailed error information:

```typescript theme={null}
import { HttpErrorResponse } from '@angular/common/http';

http.get('/api/users').subscribe({
  next: (data) => console.log(data),
  error: (error: HttpErrorResponse) => {
    console.log('Error status:', error.status);        // HTTP status code
    console.log('Error message:', error.message);      // Error message
    console.log('Error body:', error.error);           // Response body
    console.log('Error headers:', error.headers);      // Response headers
    console.log('Error url:', error.url);              // Request URL
    console.log('Error name:', error.name);            // Error name
    console.log('Error statusText:', error.statusText); // Status text
  }
});
```

## Basic error handling

### In subscriptions

Handle errors directly in the subscribe method:

```typescript theme={null}
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';

export class UserService {
  private http = inject(HttpClient);

  getUsers() {
    this.http.get<User[]>('/api/users').subscribe({
      next: (users) => {
        console.log('Users loaded:', users);
      },
      error: (error: HttpErrorResponse) => {
        if (error.status === 404) {
          console.error('Users not found');
        } else if (error.status === 500) {
          console.error('Server error occurred');
        } else {
          console.error('An error occurred:', error.message);
        }
      },
      complete: () => {
        console.log('Request completed');
      }
    });
  }
}
```

### Using catchError

Handle errors in the Observable pipe:

```typescript theme={null}
import { catchError, of } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

export class UserService {
  private http = inject(HttpClient);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      catchError((error: HttpErrorResponse) => {
        console.error('Error loading users:', error);
        // Return empty array as fallback
        return of([]);
      })
    );
  }
}
```

## Centralized error handling

### Error handler service

Create a service to centralize error handling logic:

```typescript error-handler.service.ts theme={null}
import { Injectable } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';

@Injectable({ providedIn: 'root' })
export class ErrorHandlerService {
  handleError(error: HttpErrorResponse): Observable<never> {
    let errorMessage = 'An unknown error occurred';

    if (error.error instanceof ErrorEvent) {
      // Client-side error
      errorMessage = `Client Error: ${error.error.message}`;
    } else {
      // Server-side error
      switch (error.status) {
        case 400:
          errorMessage = 'Bad Request: Please check your input';
          break;
        case 401:
          errorMessage = 'Unauthorized: Please log in';
          break;
        case 403:
          errorMessage = 'Forbidden: You do not have permission';
          break;
        case 404:
          errorMessage = 'Not Found: The requested resource does not exist';
          break;
        case 500:
          errorMessage = 'Internal Server Error: Please try again later';
          break;
        case 503:
          errorMessage = 'Service Unavailable: Server is temporarily down';
          break;
        default:
          errorMessage = `Server Error (${error.status}): ${error.message}`;
      }
    }

    console.error('HTTP Error:', errorMessage, error);
    return throwError(() => new Error(errorMessage));
  }
}
```

Use the service in your HTTP calls:

```typescript user.service.ts theme={null}
import { Injectable, inject } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { catchError, Observable } from 'rxjs';
import { ErrorHandlerService } from './error-handler.service';

@Injectable({ providedIn: 'root' })
export class UserService {
  private http = inject(HttpClient);
  private errorHandler = inject(ErrorHandlerService);

  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      catchError(this.errorHandler.handleError)
    );
  }

  getUser(id: number): Observable<User> {
    return this.http.get<User>(`/api/users/${id}`).pipe(
      catchError(this.errorHandler.handleError)
    );
  }
}
```

### Error interceptor

Handle errors globally with an interceptor:

```typescript error.interceptor.ts theme={null}
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { catchError, throwError } from 'rxjs';
import { Router } from '@angular/router';
import { ToastService } from '../services/toast.service';

export const errorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const toast = inject(ToastService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      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}`;

        // Handle specific status codes
        switch (error.status) {
          case 401:
            // Redirect to login
            router.navigate(['/login']);
            toast.error('Your session has expired. Please log in again.');
            break;
          case 403:
            toast.error('You do not have permission to perform this action.');
            break;
          case 404:
            toast.error('The requested resource was not found.');
            break;
          case 500:
            toast.error('A server error occurred. Please try again later.');
            break;
        }
      }

      console.error('HTTP Error:', errorMessage);
      return throwError(() => error);
    })
  );
};
```

Register the interceptor:

```typescript app.config.ts theme={null}
import { ApplicationConfig } from '@angular/core';
import { provideHttpClient, withInterceptors } from '@angular/common/http';
import { errorInterceptor } from './interceptors/error.interceptor';

export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(
      withInterceptors([errorInterceptor])
    )
  ]
};
```

## Retry logic

### Simple retry

Retry failed requests automatically:

```typescript theme={null}
import { retry } from 'rxjs/operators';

export class UserService {
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      retry(3), // Retry up to 3 times
      catchError(this.handleError)
    );
  }
}
```

### Conditional retry

Retry only for specific error types:

```typescript theme={null}
import { retry, timer } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

export class UserService {
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      retry({
        count: 3,
        delay: (error: HttpErrorResponse, retryCount) => {
          // Only retry on 5xx server errors
          if (error.status >= 500 && error.status < 600) {
            console.log(`Retry attempt ${retryCount}`);
            // Exponential backoff: 1s, 2s, 4s
            return timer(1000 * Math.pow(2, retryCount - 1));
          }
          // Don't retry for client errors
          throw error;
        }
      }),
      catchError(this.handleError)
    );
  }
}
```

### Advanced retry with retryWhen

```typescript theme={null}
import { retryWhen, delay, take, tap } from 'rxjs/operators';
import { throwError, timer } from 'rxjs';

export class UserService {
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      retryWhen(errors =>
        errors.pipe(
          tap(error => console.log('Error occurred:', error)),
          delay(1000),      // Wait 1 second before retry
          take(3),          // Try up to 3 times
          tap(() => console.log('Retrying...'))
        )
      ),
      catchError(this.handleError)
    );
  }
}
```

## Timeout handling

Set timeouts for requests:

```typescript theme={null}
import { timeout, catchError } from 'rxjs/operators';
import { TimeoutError } from 'rxjs';

export class UserService {
  getUsers(): Observable<User[]> {
    return this.http.get<User[]>('/api/users').pipe(
      timeout(5000), // 5 second timeout
      catchError((error) => {
        if (error instanceof TimeoutError) {
          console.error('Request timed out');
          return of([]);
        }
        return throwError(() => error);
      })
    );
  }
}
```

Or use the `timeout` option in the request:

```typescript theme={null}
http.get<User[]>('/api/users', {
  timeout: 5000
}).pipe(
  catchError(error => {
    console.error('Request timed out or failed');
    return of([]);
  })
);
```

## User feedback

### Toast notifications

```typescript user.component.ts theme={null}
import { Component, inject } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, EMPTY } from 'rxjs';
import { ToastService } from '../services/toast.service';
import { UserService } from '../services/user.service';

@Component({
  selector: 'app-users',
  template: `
    <button (click)="loadUsers()">Load Users</button>
    <div *ngFor="let user of users">
      {{ user.name }}
    </div>
  `
})
export class UsersComponent {
  private userService = inject(UserService);
  private toast = inject(ToastService);
  users: User[] = [];

  loadUsers() {
    this.userService.getUsers().pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 404) {
          this.toast.warning('No users found');
        } else if (error.status >= 500) {
          this.toast.error('Server error. Please try again later.');
        } else {
          this.toast.error('Failed to load users');
        }
        return EMPTY; // Complete the observable
      })
    ).subscribe(users => {
      this.users = users;
      this.toast.success('Users loaded successfully');
    });
  }
}
```

### Loading states

Show loading and error states:

```typescript user.component.ts theme={null}
import { Component, inject, signal } from '@angular/core';
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, finalize, EMPTY } from 'rxjs';

@Component({
  selector: 'app-users',
  template: `
    @if (loading()) {
      <div>Loading...</div>
    }
    
    @if (error()) {
      <div class="error">
        {{ error() }}
        <button (click)="loadUsers()">Retry</button>
      </div>
    }
    
    @if (!loading() && !error()) {
      <div *ngFor="let user of users()">
        {{ user.name }}
      </div>
    }
  `
})
export class UsersComponent {
  private userService = inject(UserService);
  
  users = signal<User[]>([]);
  loading = signal(false);
  error = signal<string | null>(null);

  ngOnInit() {
    this.loadUsers();
  }

  loadUsers() {
    this.loading.set(true);
    this.error.set(null);

    this.userService.getUsers().pipe(
      catchError((error: HttpErrorResponse) => {
        const message = error.status === 404 
          ? 'No users found' 
          : 'Failed to load users. Please try again.';
        this.error.set(message);
        return EMPTY;
      }),
      finalize(() => this.loading.set(false))
    ).subscribe(users => {
      this.users.set(users);
    });
  }
}
```

## Specific error scenarios

### Authentication errors

```typescript theme={null}
import { HttpInterceptorFn, HttpErrorResponse } from '@angular/common/http';
import { inject } from '@angular/core';
import { Router } from '@angular/router';
import { catchError, throwError } from 'rxjs';
import { AuthService } from '../services/auth.service';

export const authErrorInterceptor: HttpInterceptorFn = (req, next) => {
  const router = inject(Router);
  const authService = inject(AuthService);

  return next(req).pipe(
    catchError((error: HttpErrorResponse) => {
      if (error.status === 401) {
        // Clear session and redirect to login
        authService.logout();
        router.navigate(['/login'], {
          queryParams: { returnUrl: router.url }
        });
      }
      return throwError(() => error);
    })
  );
};
```

### Network errors

```typescript theme={null}
import { catchError, of } from 'rxjs';
import { HttpErrorResponse } from '@angular/common/http';

export class DataService {
  getData(): Observable<Data[]> {
    return this.http.get<Data[]>('/api/data').pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 0) {
          // Network error or CORS issue
          console.error('Network error - check your connection');
          // Return cached data if available
          const cachedData = this.getCachedData();
          return of(cachedData);
        }
        return throwError(() => error);
      })
    );
  }
}
```

### Validation errors

Handle validation errors from the server:

```typescript theme={null}
import { HttpErrorResponse } from '@angular/common/http';
import { catchError, throwError } from 'rxjs';

interface ValidationError {
  field: string;
  message: string;
}

export class FormService {
  submitForm(data: FormData): Observable<Response> {
    return this.http.post<Response>('/api/submit', data).pipe(
      catchError((error: HttpErrorResponse) => {
        if (error.status === 422) {
          // Unprocessable entity - validation errors
          const validationErrors = error.error.errors as ValidationError[];
          validationErrors.forEach(err => {
            console.error(`Validation error on ${err.field}: ${err.message}`);
          });
        }
        return throwError(() => error);
      })
    );
  }
}
```

## Testing error handling

```typescript user.service.spec.ts theme={null}
import { TestBed } from '@angular/core/testing';
import { HttpErrorResponse } from '@angular/common/http';
import { HttpTestingController, provideHttpClientTesting } from '@angular/common/http/testing';
import { provideHttpClient } from '@angular/common/http';
import { UserService } from './user.service';

describe('UserService Error Handling', () => {
  let service: UserService;
  let httpMock: HttpTestingController;

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [
        provideHttpClient(),
        provideHttpClientTesting(),
        UserService
      ]
    });

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

  afterEach(() => {
    httpMock.verify();
  });

  it('should handle 404 error', () => {
    service.getUsers().subscribe({
      next: () => fail('should have failed'),
      error: (error: HttpErrorResponse) => {
        expect(error.status).toBe(404);
        expect(error.statusText).toBe('Not Found');
      }
    });

    const req = httpMock.expectOne('/api/users');
    req.flush('User not found', {
      status: 404,
      statusText: 'Not Found'
    });
  });

  it('should handle network error', () => {
    service.getUsers().subscribe({
      next: () => fail('should have failed'),
      error: (error: HttpErrorResponse) => {
        expect(error.error).toBeInstanceOf(ProgressEvent);
        expect(error.status).toBe(0);
      }
    });

    const req = httpMock.expectOne('/api/users');
    req.error(new ProgressEvent('Network error'));
  });
});
```

## Best practices

<Tip>
  **Use centralized error handling**

  Implement a global error handler service or interceptor for consistent error handling across your application.
</Tip>

<Tip>
  **Provide meaningful feedback**

  Show user-friendly error messages instead of raw HTTP error messages.
</Tip>

<Warning>
  Don't swallow errors silently. Always log errors or notify users when something goes wrong.
</Warning>

<Tip>
  **Implement retry logic carefully**

  Only retry idempotent operations (GET, PUT, DELETE) and avoid retrying POST requests that might create duplicate resources.
</Tip>

<Tip>
  **Handle different error types**

  Differentiate between network errors (status 0), client errors (4xx), and server errors (5xx) for appropriate handling.
</Tip>

<Tip>
  **Use timeouts**

  Always set reasonable timeouts to prevent requests from hanging indefinitely.
</Tip>

## Next steps

<CardGroup cols={2}>
  <Card title="HttpClient" icon="globe" href="/http/http-client">
    Learn about making HTTP requests with HttpClient.
  </Card>

  <Card title="Interceptors" icon="filter" href="/http/interceptors">
    Transform requests and responses with interceptors.
  </Card>
</CardGroup>
