In this diagram, we have the Angular application that makes an HTTP request to the backend; in the interceptor pattern, we have an Angular service in the middle of the request that can change both the request and the return from the backend.
We will refactor our previous solution to see this pattern in practice. We’ll clean up the ExerciseSetsService service by removing the handling from the Authorization header:
export class ExerciseSetsService {
private httpClient = inject(HttpClient);
private url = ‘http://localhost:3000/diary’;
getInitialList(): Observable<ExerciseSetListAPI> {
return this.httpClient.get<ExerciseSetListAPI>(this.url);
}
}
To create the interceptor, we are going to use the Angular CLI for Angular to create the entire boilerplate of the service:
ng g interceptor login/auth
With the AuthInterceptor service created, let’s create our logic to attach the Authorization header:
@Injectable()
export class AuthInterceptor implements HttpInterceptor {
private authService = inject(AuthService);
intercept(
request: HttpRequest<unknown>,
next: HttpHandler
): Observable<HttpEvent<unknown>> {
const token = this.authService.token;
if (request.url.includes(‘auth’)) {
return next.handle(request);
}
if (token) {
const reqAuth = request.clone({
headers: request.headers.set(`Authorization`, `Bearer ${token}`),
});
return next.handle(reqAuth);
}
return next.handle(request);
}
}
The first thing we can notice is that the interceptor is a common Angular service, so it has the @Injectable notation; for more details about Angular services, see Chapter 5, Angular Services and the Singleton Pattern.
This service implements the HttpInterceptor interface, which requires the class to have the inject method. This method receives the request we want to handle and expects an observable as a return. This signature indicates the characteristic of the interceptor because this class is always in the middle of a flow between the component making the request and the backend.
Therefore, the service receives information from the flow and must return the flow represented by the observable. In our case, we use the AuthService service to get the token. The service cannot attach the token to the login endpoint because that is where we will get the token, so we make an if statement by analyzing which URL the request is using.
If we have a token, we clone the request, but this time, we inform the header with the token. The reason we need to use the clone method to get a new object is that the request object is immutable – that is, it is not possible to change it; we need to create a new one, identical to the old one, but this time, we put the header.
Finally, the flow is returned but, this time, with the new request object. To configure the interceptor, we need to change the AppModule module:
@NgModule({
declarations: [AppComponent, ErrorPageComponent],
imports: [BrowserModule, AppRoutingModule, HttpClientModule],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
],
bootstrap: [AppComponent],
})
export class AppModule {}
We’re including the AuthInterceptor service in the HTTP_INTERCEPTORS token. This tells the framework to call the service whenever a component uses Angular’s HttpClient service. The multi attribute informs the framework that we can have more than one interceptor because, by default, Angular adds only one.
Running the application again, we can see that it is working now with the addition that all the resources are attaching the header, but implicitly, without the need to change each HttpClient call.
Let’s explore this feature further with a very common task in our project, which is URL routing in the API call.