Angular interceptors are a powerful mechanism that allows you to intercept HTTP requests and responses. They are a part of Angular’s HTTP client module and can be used to perform various tasks, such as adding headers to requests, handling errors, or, in our case, logging network calls.
Let’s start by creating a new interceptor that will log network calls to the session storage. Open your terminal and run the following command to generate a new interceptor:
ng generate interceptor logger
This command will generate a new interceptor file named logger.interceptor.ts in the src/app folder.
The implementation will look sort of like this , which just includes all the boilerplate angular adds when you generate the interceptor via the CLI

We’re going to modify this slightly to give us access to both request and response events and this will make it easier for us to add our logic for storing both our request and response data into session storage.
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpResponse
} from '@angular/common/http';
import { Observable, tap } from 'rxjs';
@Injectable()
export class LoggerInterceptor implements HttpInterceptor {
constructor() {
// TODO dependency injection for session storage service
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(
tap({
next: (event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
// TODO session storage implementation
}
},
error: (error) => {
}
}))
}
}
Now that we have our foundation for our interceptor let’s pivot slightly and add a service for storing network logs in session storage and processing those logs to generate diagnostics and insights into our network activity
I cover the full implementation in a separate blog so please check this article here if you want to find out more about the session storage implementation and some of the other moving parts of this service.
https://runninghill.co.za/creating-a-generic-session-storage-implementation-in-angular/
import { Injectable } from '@angular/core';
export const REQUEST_ARRAY_KEY = "reqObjectArray"
export const RESPONSE_ARRAY_KEY = "resObjectArray"
export interface RequestArray {
size: string
method: string
responseType: string
url: string
urlWithParams: string
count: number
}
@Injectable({
providedIn: 'root'
})
export class SessionStorageService {
constructor() { }
getItem(key: string) {
try {
const result = JSON.parse(sessionStorage.getItem(key) || '{}');
return result;
} catch (error: any) {
console.log(error);
}
}
setItem(key: string, value: string | any) {
try {
return sessionStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.log(error);
}
}
removeItem(key: string) {
try {
return sessionStorage.removeItem(key);
} catch (error) {
console.log(error);
}
}
clearEntireSession() {
sessionStorage.clear();
}
setArrayItem(key: string, value: any) {
let array = this.getItem(key);
if (array && array.length > 0) {
array.push(value);
}
else {
array = [value];
}
this.setItem(key, array);
}
reduceAndCountRequests(requestList: RequestArray[]) {
let dedupe: RequestArray[] = [];
dedupe = this.removeDuplicates(requestList, "urlWithParams")
dedupe.forEach(element => {
const propValues = this.findOccurrences(requestList, "urlWithParams", element.urlWithParams)
element.count = propValues
});
dedupe.sort((a, b) => b.count - a.count);
return dedupe;
}
findOccurrences(arr: any[], prop: string, value: string) {
const matches = arr.filter(obj => obj[prop] === value);
return matches.length;
}
removeDuplicates = (arr: RequestArray[], prop: keyof RequestArray): RequestArray[] => {
const seen: { [key: string]: boolean } = {};
return arr.reduce((acc: RequestArray[], obj: RequestArray) => {
if (!seen[obj[prop].toString()]) {
seen[obj[prop].toString()] = true;
acc.push(obj);
}
return acc;
}, []);
}
retrieveRequestObjectFromStorage() {
const resData = this.getItem(RESPONSE_ARRAY_KEY)
const reqData: RequestArray[] = this.getItem(REQUEST_ARRAY_KEY)
const requestData = this.reduceAndCountRequests(reqData)
const responseData = this.reduceAndCountRequests(resData)
return { requestData, responseData }
}
}
Let’s break down some of the code implemented here so we understand what we’re trying to achieve. As mentioned I’ve covered the implementation of this as a generic service in a separate blog so I’m only going to break down the additional logic I added for processing the network logs.
setArrayItem(key: string, value: any)This function is responsible for storing an item in an array within sessionStorage. It takes a key and a value as parameters. Here’s what it does:
key using the getItem method.value to it.value.sessionStorage using the setItem method.reduceAndCountRequests(requestList: RequestArray[])This function operates on an array of RequestArray objects, which represents network requests. Here’s what it does:
dedupe and assigns it the result of calling the removeDuplicates function on requestList. This removes any duplicate entries from the input array based on the urlWithParams property.dedupe array using forEach, and for each unique request object, it counts how many times it appears in the original requestList. The count is stored in the count property of each request object.dedupe array in descending order based on the count property.dedupe array, which now contains unique requests with counts indicating how many times each request occurred.This function is useful for analyzing and ranking the most frequently occurring network requests.
findOccurrences(arr: any[], prop: string, value: string)This utility function searches for the number of occurrences of a specific value within an array of objects based on a specified prop (property). It iterates through the array and filters objects where the specified property matches the given value, then returns the count of matches.
removeDuplicatesThis is a reusable utility function that removes duplicates from an array of objects based on a specified property. It uses a JavaScript reduce operation and an object seen to keep track of whether a specific value has been encountered before. It effectively deduplicates the input array based on the specified property.
retrieveRequestObjectFromStorageThis function retrieves and processes data stored in sessionStorage. Here’s what it does:
sessionStorage using the getItem method, one for request data and another for response data.reduceAndCountRequests function to obtain unique requests sorted by their occurrence count.In summary, these functions provide a way to manage and analyze network request data stored in sessionStorage. They help with tasks like counting request occurrences, removing duplicates, and preparing the data for analysis or presentation.
After adding our session storage implementation to the interceptor, our code will look like this
import { Injectable } from '@angular/core';
import {
HttpRequest,
HttpHandler,
HttpEvent,
HttpInterceptor,
HttpResponse
} from '@angular/common/http';
import { Observable, tap } from 'rxjs';
import { SessionStorageService, REQUEST_ARRAY_KEY, RESPONSE_ARRAY_KEY } from './session-storage.service';
@Injectable()
export class LoggerInterceptor implements HttpInterceptor {
constructor(private sessionStorageService: SessionStorageService) {
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
const startTime = Date.now();
// Modify the request here if needed
const { body, method, responseType, url, urlWithParams } = req;
const stringSize = JSON.stringify(body).length;
const size = this.charactersToKilobytes(stringSize) + ' kb'
this.sessionStorageService.setArrayItem(REQUEST_ARRAY_KEY, { method, responseType, url, urlWithParams, size, count: 1 });
return next.handle(req).pipe(
tap({
next: (event: HttpEvent<any>) => {
if (event instanceof HttpResponse) {
const { body, status, url } = event;
const urlWithParams = url
const endTime = Date.now();
const timeTaken = (endTime - startTime) + ' ms'
const stringSize = JSON.stringify(body).length; // rough estimate in characters
const size = this.charactersToKilobytes(stringSize) + ' kb'
this.sessionStorageService.setArrayItem(RESPONSE_ARRAY_KEY, { urlWithParams, status, timeTaken, size, count: 1 })
}
},
error: (error) => {
// Handle or log errors globally here
console.error('Error occurred:', error);
}
}))
}
charactersToKilobytes(numCharacters: number) {
const bytes = numCharacters * 2; // UTF-16 encoding
return bytes / 1024;
}
}
Let’s break down what this code does:
const startTime = Date.now();
// Modify the request here if needed
const { body, method, responseType, url, urlWithParams } = req;
const stringSize = JSON.stringify(body).length;
const size = this.charactersToKilobytes(stringSize) + ' kb'
this.sessionStorageService.setArrayItem(REQUEST_ARRAY_KEY, { method, responseType, url, urlWithParams, size, count: 1 });
startTime is a timestamp captured when the request is initiated using Date.now(). This will be used to calculate the time taken for the request.body, method, responseType, url, and urlWithParams are extracted from the req object, which represents the outgoing HTTP request.stringSize calculates the size of the request body by converting it to a JSON string and measuring its length in characters.size converts the stringSize to kilobytes using a method called charactersToKilobytesthis.sessionStorageService.setArrayItem is used to store this request information in sessionStorage under the REQUEST_ARRAY_KEY. It includes properties such as the request method, response type, URL, URL with parameters, size, and an initial count of 1 (indicating this is the first occurrence of the request).const { body, status, url } = event;
const urlWithParams = url
const endTime = Date.now();
const timeTaken = (endTime - startTime) + ' ms'
const stringSize = JSON.stringify(body).length; // rough estimate in characters
const size = this.charactersToKilobytes(stringSize) + ' kb'
this.sessionStorageService.setArrayItem(RESPONSE_ARRAY_KEY, { urlWithParams, status, timeTaken, size, count: 1 })
body, status, and url are extracted from the event object, which represents the incoming HTTP response.urlWithParams is assigned the same value as url, which appears to be creating a copy of the URL.endTime is another timestamp captured when the response is received, allowing for the calculation of the time taken for the request.timeTaken calculates the time taken by subtracting startTime from endTime and appending ‘ ms’ to represent the time in milliseconds.stringSize calculates the size of the response body in a similar manner to the request.size converts the stringSize to kilobytes, just like in the request section.this.sessionStorageService.setArrayItem is used again, but this time, it stores the response information in sessionStorage under the RESPONSE_ARRAY_KEY. It includes properties such as the URL with parameters, response status, time taken, size, and an initial count of 1 (indicating this is the first occurrence of the response).The last step before we can test out our implementation is to register our interceptor
Register the interceptor in your app.module.ts file by providing it in the HTTP_INTERCEPTORS multi-provider token. Import the necessary modules and add the interceptor to the providers array in the NgModule decorator:
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { HTTP_INTERCEPTORS, HttpClientModule } from '@angular/common/http';
import { LoggerInterceptor } from './logger.interceptor';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
AppRoutingModule
],
providers: [ {
provide: HTTP_INTERCEPTORS,
useClass: LoggerInterceptor,
multi: true,
}],
bootstrap: [AppComponent]
})
export class AppModule { }
Below, I wrote a simple application which makes network requests in a loop, you can observe both our session storage logs and the result of the utility functions we wrote to process the request information and generate diagnostics.
If you would like to see this code and run it locally for yourself please find the repo here
https://github.com/Yashlin-Naidoo/BaseUi.Angular/tree/feature/interceptor

In summary, this Angular HTTP interceptor, along with the associated session storage service, offers a robust solution for logging and analyzing network activity in your Angular application. You can use this approach to gain insights into your application’s performance, troubleshoot issues, and optimize your HTTP requests and responses
WRITTEN BY
Yashlin Naidoo