06/27/2019 12:00 AM
Angular HTTP Interceptors provide a flexible mechanism to control your application when dealing with network-related resources. They’re similar to middle-ware in other frameworks and allow network logic to be abstracted and reused.
Interceptors provide a convenient location to apply functionality across all or some network requests and responses. As an application grows, re-implementing the same logic can become tedious, error-prone, and potentially leads to inconsistent functionality.
For example, setting up an Authorization Header across multiple network requests can quickly lead to duplicate code at the service or component level. By utilizing an interceptor it can be configured once and applied on all existing and future HTTP requests. By abstracting global network logic to a single responsibility class, we make it easier to test and build reliable applications quickly.
Requests can be controlled with the HttpHandler that is passed to interceptor methods. In its simplest usage, if you don’t want to modify the request, you return the handle method. Typically used like this:
To interact with the response, you’ll pipe off the handle method. From there you can interact with other services such as adding to a cache like you’ll see below or modifying the response in the XML to JSON example.
Be aware that there may be other events besides the response so it’s a good idea to check for an HttpResponse before acting on the response event.
Caching, specifically cache invalidation, can be quite difficult so this article will skip the specifics on that. Instead, we’ll look at a simple example that returns something from the cache or allows the request to go through. For a given endpoint you can cache the results in a number of ways and return the cache based on time, existence, or another factor.
The following example checks for the existence in the cache and returns it:
If the request is not in the cache, make the request as normal with sendRequest.
Here we pipe off the handle method to interact with the response and set it in the cache. Now if the same request is made it will be returned immediately.
Often developers don’t have complete control over how they get data. For example, if you have one API that returns XML but the rest of your app works with JSON it might make sense to convert the XML response to JSON for consistency. With an interceptor, the XML conditional logic can be abstracted from consumers and applied to all network requests.
In order to handle the response, this logic is pipe’ed off of the handle method. Then if the response is valid XML, we return the cloned response event and parse the XML into JSON.
When an application needs to restrict access to certain routes, an interceptor can provide that functionality in one place across many routes. Since interceptors are run for every request you can organize your route guard logic in one place. Assuming you’ve set up what routes are guarded and can access the user’s available scopes, the interceptor can determine whether to allow the request or redirect.
In this example, if the route isn’t protected we don’t take any action. If it is the request is allowed if the user is an admin otherwise we redirect and cancel the request.
It may not be obvious but the HttpRequest and HttpResponse instances are immutable. From the Angular docs
Interceptors are capable of mutating requests and responses, the HttpRequest and HttpResponse instance properties are read-only, rendering them largely immutable.
This is done because the app may retry a request several times and if the interceptor could modify the original request then each subsequent request could be different.
What this means for developers is that you must clone the request and modify that instance:
Interceptors pass requests through in the order that they’re provided [A,B,C]. The following “barrel” example gathers interceptors to later be provided in their declared order.
Requests will flow in A->B->C and responses will flow out C->B->A.
Then in app.module.ts:
As we’ve seen in these examples, interceptors provide a straightforward mechanism to interact with HTTP requests and responses. This makes it easy to add layers of control and provide more functionality throughout an application without duplicating logic.
Originally published at kevinschuchard.com on May 8, 2019.
Thanks to Jon Rista.
Author: Kevin Schuchard, Sr. Enterprise Software Engineer