Angular  

How to Use Angular Interceptors for API Calls

Making API calls is one of the most common tasks in Angular applications. Whether you are building a simple CRUD interface or a large-scale enterprise system, your app will frequently communicate with backend services using HTTP requests. While Angular provides the HttpClient service for making calls, repeating logic such as attaching authentication tokens, handling errors, logging requests, or modifying headers across multiple services becomes difficult to maintain.

This is where Angular Interceptors become extremely powerful.

Angular Interceptors allow you to intercept outgoing HTTP requests and incoming responses. This means you can centralize common logic and keep your application clean and maintainable.

This article provides a complete, step-by-step explanation of Angular Interceptors, how they work, common use cases, and implementation examples with best practices.

1. What Are Angular Interceptors

Angular Interceptors are classes that implement the HttpInterceptor interface. They act like middleware that sits between your app and the backend API.

When you make an HTTP request:

  1. The request first passes through all configured interceptors.

  2. The backend API processes it.

  3. The response then passes back through the interceptors in reverse order.

This flow allows you to:

  • Automatically add authentication headers

  • Log request or response details

  • Catch and manage errors globally

  • Modify request URLs dynamically

  • Handle loading indicators

  • Retry failed requests

In short, interceptors help in centralizing reusable logic for all API calls.

2. Creating Your First Angular Interceptor

Let’s start by creating a basic interceptor using the Angular CLI.

ng generate interceptor interceptors/auth

This creates:

src/app/interceptors/auth.interceptor.ts

Inside the generated file, you get a default structure.

2.1 Basic Interceptor Template

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

@Injectable()
export class AuthInterceptor implements HttpInterceptor {
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req);
  }
}

This is the simplest form, where the request passes unchanged to the next handler.

3. Adding Authorization Headers Automatically

One of the most common uses of interceptors is attaching authentication tokens (JWT tokens) to all outgoing requests.

3.1 Example: Adding Authorization Header

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const token = localStorage.getItem('token');

    const modifiedRequest = req.clone({
      setHeaders: {
        Authorization: `Bearer ${token}`
      }
    });

    return next.handle(modifiedRequest);
  }
}

Now, every HTTP request automatically gets the Authorization header attached.

4. Registering the Interceptor in App Module

Angular does not enable interceptors automatically. You must register them in the providers array.

4.1 Add in app.module.ts

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

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

The multi: true property ensures Angular can use multiple interceptors.

5. Logging HTTP Requests and Responses

Interceptors can also be used to log data for debugging or analytics.

5.1 Logging Interceptor Example

@Injectable()
export class LoggingInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    console.log('Outgoing request:', req.url, req.method);

    return next.handle(req).pipe(
      tap(event => {
        if (event.type === 4) {
          console.log('Response received:', event);
        }
      })
    );
  }
}

This logs every request and response passing through the interceptor.

6. Global Error Handling with Interceptors

Handling errors in every service file is repetitive. Instead, you can catch errors globally in one place.

6.1 Error Handling Example

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

@Injectable()
export class ErrorInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    return next.handle(req).pipe(
      catchError(err => {
        if (err.status === 401) {
          console.log('Unauthorized access');
        }

        if (err.status === 500) {
          console.log('Server error occurred');
        }

        return throwError(() => err);
      })
    );
  }
}

This approach keeps your services clean.

7. Adding a Loader / Spinner Using Interceptors

Many applications require showing a loading spinner while API calls are in progress. This can be handled in one place using an interceptor.

7.1 Loader Example

@Injectable()
export class LoaderInterceptor implements HttpInterceptor {

  private requestCount = 0;

  constructor(private loaderService: LoaderService) {}

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    this.requestCount++;
    this.loaderService.show();

    return next.handle(req).pipe(
      finalize(() => {
        this.requestCount--;
        if (this.requestCount === 0) {
          this.loaderService.hide();
        }
      })
    );
  }
}

This ensures the loader indicator appears and disappears correctly.

8. Retrying Failed HTTP Requests

Interceptors can automatically retry failed calls due to network issues.

8.1 Retry Example

import { retry } from 'rxjs/operators';

@Injectable()
export class RetryInterceptor implements HttpInterceptor {

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    return next.handle(req).pipe(
      retry(2)
    );
  }
}

This retries the request twice before failing.

9. Modifying Request URLs

You may need to prefix all API URLs with a base URL.

9.1 Example: Adding Base URL

@Injectable()
export class ApiPrefixInterceptor implements HttpInterceptor {

  private baseUrl = 'https://api.example.com/';

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

    const updatedRequest = req.clone({
      url: this.baseUrl + req.url
    });

    return next.handle(updatedRequest);
  }
}

This allows you to use short URLs in your services.

10. Order of Interceptors

Angular executes interceptors in the order they are provided:

Outgoing Request Order
Interceptor1
Interceptor2
Interceptor3

Incoming Response Order
Interceptor3
Interceptor2
Interceptor1

This means registration order influences behavior.

11. Full Real-World Example Setup

In a real application, you might use four interceptors:

  1. ApiPrefixInterceptor

  2. AuthInterceptor

  3. LoggingInterceptor

  4. ErrorInterceptor

11.1 Register All Interceptors

providers: [
  { provide: HTTP_INTERCEPTORS, useClass: ApiPrefixInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: LoggingInterceptor, multi: true },
  { provide: HTTP_INTERCEPTORS, useClass: ErrorInterceptor, multi: true }
]

This setup centralizes all API-related logic.

12. Best Practices When Using Angular Interceptors

  1. Keep interceptors focused on one responsibility.

  2. Do not perform heavy logic inside interceptors.

  3. Avoid multiple clone operations unless necessary.

  4. Use interceptors for cross-cutting concerns only.

  5. Always use multi: true when registering.

  6. Combine logging and error handling carefully to avoid duplication.

  7. Do not modify responses unnecessarily.

13. When Should You Use Interceptors

Use interceptors when you need to:

  • Attach tokens to requests

  • Log requests or responses

  • Catch global errors

  • Add base URLs

  • Show and hide loaders

  • Implement retry strategies

  • Transform requests or responses

They help simplify your codebase significantly.

14. When Not To Use Interceptors

Avoid using interceptors when:

  • You need feature-specific logic (use services instead)

  • You require user interaction inside the interceptor

  • You want to handle business logic (interceptors are not for business rules)

  • The modification applies only to one endpoint

Use interceptors only for global logic.

Conclusion

Angular Interceptors are one of the most powerful features available to simplify HTTP communication across your application. By using interceptors, you can centralize repeated tasks such as adding headers, handling errors, logging, and managing loaders. This helps maintain a clean architecture and reduces redundancy in your services.

You now understand:

  • What interceptors are

  • How they work

  • How to create and register them

  • Real-world use cases

  • Best practices and limitations