DEV Community

Cover image for I Built the Ultimate HTTP Interceptor for Angular: 6 Resilience Patterns and RxJS in Practice

I Built the Ultimate HTTP Interceptor for Angular: 6 Resilience Patterns and RxJS in Practice

If you develop complex Angular applications - like dynamic dashboard systems with multiple widgets or node-based interfaces to map process rules - you know that the network is the most hostile environment in the frontend.

Simultaneous requests fighting for the same URL, unstable connections, server slowdowns, and the dreaded 401 error breaking dozens of parallel calls. The classic approach is to create a giant HttpInterceptor, which quickly becomes an unmaintainable "God Object".

To solve this by applying SOLID principles (focusing heavily on the SRP - Single Responsibility Principle), I developed ngx-smart-interceptor.

Instead of processing rules directly, the main interceptor acts merely as an Orchestrator, delegating the heavy RxJS lifting to specialist Handlers. I created a Sandbox to stress-test the library, and here are the 6 pillars of resilience it delivers with just a few lines of configuration.


1. Request Deduplication (In-Flight Caching)

In complex grids, it's common for multiple components to request the same data dictionary (e.g., GET /api/status) at the same time. The DeduplicationHandler steps in using the shareReplay(1) RxJS operator.

If your code fires 3 simultaneous requests, only one real call goes to the Network tab:

// In your component, even if you force simultaneous calls...
ngOnInit() {
  forkJoin({
    widgetA: this.http.get('/api/config'),
    widgetB: this.http.get('/api/config'),
    widgetC: this.http.get('/api/config')
  }).subscribe(res => {
    // All 3 receive the data at the same time,
    // but the server only received 1 request!
    console.log(res);
  });
}
Enter fullscreen mode Exit fullscreen mode

2. Circuit Breaker Pattern

If your heavy business intelligence API goes down (500 Error), the frontend shouldn't bombard the server with blind retries.

The CircuitBreakerHandler watches for consecutive failures. Upon reaching the threshold (e.g., 3 failures), it "trips" the circuit. New requests are blocked directly in the browser (without hitting the network) and a local error is returned instantly, giving the server time to breathe and recover.


3. Offline Queue

Imagine a user building a complex diagram and clicking "Save" (POST) right when the Wi-Fi drops.

With enableOfflineQueue active, the interceptor catches the network failure. It holds the POST in memory, and as soon as the browser emits the online event, it silently reprocesses the queue in the background. Your user never loses data.


4. Adaptive Loading

The interceptor features a network profiler that notices when the connection is throttled (high latency).

It automatically injects the X-Adaptive-Network: slow header into the request. In your backend (whether Node, C#, or Java), you can read this header and return a smaller payload to save the user experience:

// Example in the Backend (Express.js) consuming the header
app.get('/api/dashboard', (req, res) => {
  const isSlowNetwork = req.headers['x-adaptive-network'] === 'slow';

  if (isSlowNetwork) {
    return res.json(getLightweightData()); // Without heavy images
  }
  return res.json(getFullData());
});
Enter fullscreen mode Exit fullscreen mode

5. Observability & Profiler

If an API call takes more than 3 seconds, the interceptor triggers a degradation alert (Slow Request Warning) directly in the console or to your monitoring service (like Sentry).


6. Global Hooks and Normalized Errors

The terror of Angular is dealing with the native HttpErrorResponse object scattered across catchError blocks in your components. The Orchestrator normalizes errors and allows the injection of Global Hooks to handle critical situations (like a 401 Unauthorized error) in a single place.

Here is how easy it is to configure Token renewal globally:

// app.config.ts
export const appConfig: ApplicationConfig = {
  providers: [
    provideHttpClient(withInterceptors([smartInterceptor])),
    provideSmartInterceptor({
      generateCorrelationIds: true,
      globalHooks: {
        // Intercepts ALL 401s in the application
        onUnauthorized: (err, req) => {
          console.warn('Session expired. Redirecting or renewing token...');
          const authService = inject(AuthService);
          authService.logout();
        }
      }
    })
  ]
};
Enter fullscreen mode Exit fullscreen mode

How does the code look day-to-day?

The beauty of using a smart interceptor is that your component code becomes extremely clean.

You don't need to write complex retry logic or handle obscure network messages. The interceptor injects a userFriendlyMessage and a correlationId for traceability:

// dashboard.component.ts
saveFlow() {
  this.http.post('/api/process', this.payload).pipe(
    catchError((error) => {
      // The interceptor has already handled Circuit Breaker blocks or network failures.
      // The error arrives clean and standardized to the UI:

      this.toastService.showError(error.userFriendlyMessage);
      console.error(`Traceable failure: ${error.correlationId}`);

      return EMPTY;
    })
  ).subscribe(() => {
    this.toastService.showSuccess('Flow saved successfully!');
  });
}
Enter fullscreen mode Exit fullscreen mode

Conclusion

Building robust libraries is about mastering the ecosystem's tools and applying Software Engineering patterns. ngx-smart-interceptor proves that it's possible to transform the chaotic frontend network layer into a predictable, resilient, and highly testable pipeline.

I invite you to test the library, check out the full API documentation generated via Compodoc, and analyze the source code architecture (which features a complete automated CI/CD pipeline).

GitHub: https://github.com/ErickG123/ngx-smart-interceptor
NPM: npm install ngx-smart-interceptor

If this library is useful for your projects, consider leaving a ⭐ on the repository! Every contribution and Pull Request from the community is highly welcome.

Top comments (0)