← Back to Articles         
Class-based Interceptors VS Functional Interceptor

Class-based Interceptors VS Functional Interceptor

Published on 01 Oct 2026     6 min read Angular
HTTP Interceptors

1. How were class-based interceptors configured using HTTP_INTERCEPTORS?

Before functional interceptors became the preferred modern approach, Angular commonly used class-based interceptors.

A class-based interceptor:

  • Is a class decorated with @Injectable()
  • Implements the HttpInterceptor interface
  • Implements the intercept() method
  • Is registered using the HTTP_INTERCEPTORS dependency-injection token
  • Uses multi: true so multiple interceptors can coexist

Full Simple Example — Class-Based Interceptor

auth.interceptor.ts

import { Injectable } from '@angular/core';

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

import { Observable } from 'rxjs';

@Injectable()
export class AuthInterceptor implements HttpInterceptor {

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

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

    if (!token) {
      return next.handle(req);
    }

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

    return next.handle(authRequest);
  }
}

Traditional app.module.ts

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

import { AppComponent } from './app.component';
import { AuthInterceptor } from './auth.interceptor';

@NgModule({

  declarations: [
    AppComponent
  ],

  imports: [
    BrowserModule,
    HttpClientModule
  ],

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

  bootstrap: [
    AppComponent
  ]

})
export class AppModule {
}

Understanding HTTP_INTERCEPTORS

HTTP_INTERCEPTORS is an Angular Dependency Injection token used to register class-based HTTP interceptors.

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Each part has a specific meaning.

provide

provide: HTTP_INTERCEPTORS

This tells Angular:

Register something as an HTTP interceptor.

useClass

useClass: AuthInterceptor

This tells Angular which interceptor class should be instantiated.

multi: true

multi: true

This is very important.

It tells Angular:

Don't replace the existing interceptor. Add this interceptor to the collection of interceptors.

Therefore, you could register several interceptors:

providers: [

  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  },

  {
    provide: HTTP_INTERCEPTORS,
    useClass: LoggingInterceptor,
    multi: true
  },

  {
    provide: HTTP_INTERCEPTORS,
    useClass: ErrorInterceptor,
    multi: true
  }

]

Conceptually:

HTTP_INTERCEPTORS
       │
       ├── AuthInterceptor
       ├── LoggingInterceptor
       └── ErrorInterceptor

Without multi: true, you would not be registering the provider as one member of the multi-provider collection.


Understanding intercept()

Every class-based interceptor implements:

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

There are two important parameters.

req

req: HttpRequest<unknown>

Represents the current HTTP request.

You can inspect:

req.url
req.method
req.headers
req.body

Because HttpRequest is immutable, modifications are normally made using:

req.clone()

next

next: HttpHandler

Represents the next step in the HTTP processing chain.

You continue processing using:

next.handle(req)

For example:

return next.handle(authRequest);

Conceptually:

Request
   ↓
AuthInterceptor.intercept()
   ↓
next.handle()
   ↓
Next Interceptor
   ↓
Web API

2. What is a Functional Interceptor?

A functional interceptor is an HTTP interceptor implemented as a function instead of a class.

It uses the type:

HttpInterceptorFn

This style fits Angular's modern standalone APIs and is generally simpler than the older class-based pattern.


Full Simple Example — Functional Interceptor

auth.interceptor.ts

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

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

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

    if (!token) {
      return next(req);
    }

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

    return next(authRequest);
  };

app.config.ts

import {
  ApplicationConfig
} from '@angular/core';

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

import {
  authInterceptor
} from './auth.interceptor';

export const appConfig: ApplicationConfig = {

  providers: [

    provideHttpClient(

      withInterceptors([
        authInterceptor
      ])

    )

  ]

};

That's enough to configure the functional interceptor.


Functional Interceptor Syntax

The basic syntax is:

export const myInterceptor: HttpInterceptorFn =
  (req, next) => {

    // Before request

    return next(req);

  };

It receives:

req
 ↓
Current HTTP Request

next
 ↓
Function that passes the request
to the next interceptor/backend

next.handle() vs next()

This is one of the most important differences to remember.

Class-based

return next.handle(req);

because next is a:

HttpHandler

Functional

return next(req);

because next is a function.

So:

Class Interceptor
-----------------
next.handle(req)


Functional Interceptor
----------------------
next(req)

Dependency Injection in Functional Interceptors

Suppose you have:

@Injectable({
  providedIn: 'root'
})
export class AuthService {

  getToken(): string | null {
    return localStorage.getItem('accessToken');
  }

}

A functional interceptor doesn't need constructor injection.

Use Angular's:

inject()

Example:

import { inject } from '@angular/core';
import { HttpInterceptorFn } from '@angular/common/http';

import { AuthService } from './auth.service';

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    const authService = inject(AuthService);

    const token = authService.getToken();

    if (!token) {
      return next(req);
    }

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

    return next(authRequest);
  };

So:

const authService = inject(AuthService);

provides access to Angular DI.


Multiple Functional Interceptors

Suppose your application contains:

Auth Interceptor
Logging Interceptor
Loading Interceptor
Error Interceptor

Register them:

provideHttpClient(
  withInterceptors([
    authInterceptor,
    loggingInterceptor,
    loadingInterceptor,
    errorInterceptor
  ])
)

The configured order defines the request-side interceptor chain.

Conceptually:

Angular HttpClient
       ↓
Auth Interceptor
       ↓
Logging Interceptor
       ↓
Loading Interceptor
       ↓
Error Interceptor
       ↓
ASP.NET Core Web API

Responses then unwind through the chain in reverse nesting order.


Class-Based vs Functional Interceptors

Class-Based Functional
Uses a class Uses a function
Implements HttpInterceptor Uses HttpInterceptorFn
Uses intercept() Function itself is interceptor
next.handle(req) next(req)
Often uses constructor DI Uses inject() when DI is needed
Registered with HTTP_INTERCEPTORS Registered with withInterceptors()
Common in older/module-based applications Preferred for modern Angular applications

The key syntax difference is:

Old class-based style

@Injectable()
export class AuthInterceptor
  implements HttpInterceptor {

  intercept(req, next) {

    return next.handle(req);

  }
}

Modern functional style

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    return next(req);

  };

The functional version removes much of the class/provider boilerplate.


Can class-based interceptors still be used?

Yes. Angular still supports DI/class-based interceptors.

With modern provideHttpClient(), DI-provided interceptors can be included using:

withInterceptorsFromDi()

For example:

import {
  provideHttpClient,
  withInterceptorsFromDi,
  HTTP_INTERCEPTORS
} from '@angular/common/http';

providers: [

  provideHttpClient(
    withInterceptorsFromDi()
  ),

  {
    provide: HTTP_INTERCEPTORS,
    useClass: AuthInterceptor,
    multi: true
  }

]

However, for new Angular code, functional interceptors are generally the preferred approach.


Key Points

Class-based interceptor

implements HttpInterceptor

implements:

intercept()

and forwards requests using:

next.handle(req)

Registration:

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

Functional interceptor

Uses:

HttpInterceptorFn

and forwards requests using:

next(req)

Registration:

provideHttpClient(
  withInterceptors([
    authInterceptor
  ])
)

Dependencies can be retrieved using:

inject(AuthService)

So the easiest way to remember the evolution is:

Older / Class-Based
────────────────────────────
@Injectable()
      ↓
implements HttpInterceptor
      ↓
intercept(req, next)
      ↓
next.handle(req)
      ↓
HTTP_INTERCEPTORS
      ↓
multi: true


Modern / Functional
────────────────────────────
HttpInterceptorFn
      ↓
(req, next)
      ↓
next(req)
      ↓
withInterceptors([...])

Interview Questions and Answers

1. How were class-based interceptors configured?

A class implemented HttpInterceptor, defined the intercept() method, and was registered using the HTTP_INTERCEPTORS multi-provider.

{
  provide: HTTP_INTERCEPTORS,
  useClass: AuthInterceptor,
  multi: true
}

2. Why is multi: true required?

HTTP_INTERCEPTORS is used as a multi-provider token. multi: true allows multiple interceptor implementations to be registered under the same token instead of treating it as a single provider.

3. What is a functional interceptor?

A functional interceptor is an Angular HTTP interceptor implemented as a function using:

HttpInterceptorFn

Example:

export const authInterceptor: HttpInterceptorFn =
  (req, next) => {

    return next(req);

  };

4. How do you inject a service into a functional interceptor?

Use Angular's inject():

const authService = inject(AuthService);

5. What is the main syntax difference?

Class-based:
next.handle(req)

Functional:
next(req)

6. How are functional interceptors registered?

Using:

provideHttpClient(
  withInterceptors([
    authInterceptor
  ])
)

Interview one-line answer

Class-based interceptors implement HttpInterceptor and are registered through the HTTP_INTERCEPTORS multi-provider, whereas modern functional interceptors use HttpInterceptorFn, can access dependencies through inject(), and are registered with provideHttpClient(withInterceptors([...])).

← Back to Articles