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
HttpInterceptorinterface - Implements the
intercept()method - Is registered using the
HTTP_INTERCEPTORSdependency-injection token - Uses
multi: trueso 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
HttpInterceptorand are registered through theHTTP_INTERCEPTORSmulti-provider, whereas modern functional interceptors useHttpInterceptorFn, can access dependencies throughinject(), and are registered withprovideHttpClient(withInterceptors([...])).