Angular interceptors are a crucial part of creating web applications. By providing multiple functions, they can change HTTP requests. Interceptors in Angular are a great solution used by Angular development companies like us for simplifying and improving the understandability of an HTTP request or response. In this blog, you will get detailed information on the Angular HTTP Interceptors, how they function, and how to create them. Let’s begin with an overview.
1. What is an Angular HTTP Interceptor?
Angular 4.3 was released with HttpInterceptor. Thanks to it, we can intercept HTTP requests and responses, allowing us to process or modify HTTP requests before sending them on.
“While Angular HTTP Interceptors can transform HTTP requests and HTTP responses, the instance properties of HttpRequest and HttpResponse are read-only, so they can’t be changed very easily,” according to Angular documentation. This could be because we need to make an effort to retry a request if the initial one doesn’t work.
Angular applies interceptors in the sequence that you specify. Requests will go via A->B->C if you specify interceptors A, B, and C in that order, and answers will go through C->B->A.
Angular interceptors cannot be removed or changed in sequence at a later time. It is necessary to incorporate the functionality to dynamically enable and disable interceptors within the interceptors themselves. When utilizing many Angular HTTP Interceptors, one must remember this.
2. Why Do We Use Interceptors?
A method to intercept and alter incoming responses or outgoing requests is provided by interceptors. Apart from the front end, they are quite comparable to the idea of middleware using a framework like Express.
For functionality like caching and logging, interceptors could prove helpful. One approach to handle every single HTTP request and response is via Angular interceptors.
- Make sure that all outbound HTTP requests include a token or a custom HTTP header.
- Preparing the data for your service or component involves catching HTTP replies and applying specific formatting, such as converting CSV to JSON.
- Keep a record of all HTTP actions in the console.
- Here you can handle all HTTP errors by handling requests based on their error status codes and doing common error handling.
A class that is injectable and executes HttpInterceptor is what you need to develop an interceptor. A noticeable type HttpEvent is returned by the intercept method with two parameters, req and next.
- The object of the request, req, is of the HTTP Request type.
- Another HTTP type is next. An HttpEvent observable of our choosing can be obtained using the handler’s handle function.
3. How Does The Interceptor Work?
Suppose it’s necessary to process each request submitted by the application’s httpClient.
For the server to verify our identity and send us the necessary response, we need to provide an authentication token in every request. To do this, we will set up a middle service that will enable us to alter our httpRequest and httpResponse as needed. Perhaps the following diagram can clarify the solution for you:
4. How to Create a Basic HTTP Interceptor
The below command can be used to generate an angular interceptor:
ng generate interceptor MyInterceptor |
Intercepting an HTTP request and response is possible using an Angular interceptor, which mimics the appearance of a service with @Injectable() and implements HttpInterceptor. Take this case into consideration:
import { Injectable } from '@angular/core'; import { HttpInterceptor, HttpEvent, HttpRequest, HttpHandler } from '@angular/common/http'; import { Observable } from 'rxjs'; @Injectable() export class MyInterceptor implements HttpInterceptor { intercept(httpRequest: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // You can manipulate the request before it is sent console.log('Intercepted Request:', httpRequest); //any alteration in httpRequest can be done here return next.handle(httpRequest); } } |
Specifically, when the intercept() method is used, Angular provides a pointer to the httpRequest object. Feel free to examine and make any necessary changes to this request. Upon completion of our logic, we return the modified request to the application by utilizing next.handle.
Furthermore, we must incorporate the modification into the @NgModule
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: MyInterceptor, multi:true }, ],}) |
4.1 Create a Service that Implements HttpInterceptor
The first step is to construct a service that uses HttpInterceptor.
import { HttpInterceptor} from '@angular/common/http'; import { Injectable } from '@angular/core'; @Injectable() export class TokenInterceptorService implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // You can manipulate the request before it is sent console.log('Intercepted Request:', httpRequest); // All HTTP requests are going to go through this method const X-TOKEN = 'f4179b26-21ac-432c-bcd8-cb4bc6e50981'; const modifiedRequest = request.clone({ setHeaders:{ X-TOKEN } }); //any alteration in httpRequest can be done here return next.handle(modifiedRequest); } } |
It is necessary to include that interceptor in the list of all HTTP_INTERCEPTORS in order to start it:
@NgModule({ ... providers: [ {provide: HTTP_INTERCEPTORS, useClass: TokenInterceptorService, multi: true}]}) export class AppModule { } |
We provide our interceptor service with “multi:true” since the number of interceptors could vary.
4.2 Intercepting an Http Request
To handle a token, we must execute the intercept method after configuring our interceptor. If our LoginService has an authentication token, an HTTP header is declared:
export class TokenInterceptorService implements HttpInterceptor { // We inject a LoginService constructor(private loginService: LoginService) {} intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // We retrieve the token, if any const token = this.loginService.getAuthToken(); let newHeaders = req.headers; if (token) { // If we have a token, we append it to our new headers newHeaders = newHeaders.append('authtoken', token); } // Finally we have to clone our request with our new headers // This is required because HttpRequests are immutable const authReq = req.clone({headers: newHeaders}); // Then we return an Observable that will run the request // or pass it to the next interceptor if any return next.handle(authReq); } } |
As a result, the code will incorporate that header into each and every HTTP request.
4.3 Intercepting a Response
You will need to cut out the handle method to communicate with the response. You may then edit the response in the XML to JSON example or connect with other services, like adding to a cache.
Before doing anything with the response event, make sure you’ve checked for a HttpResponse. Other events can be waiting for you.
return next.handle(req).pipe( // There may be other events besides the response. filter(event => event instanceof HttpResponse), tap((event: HttpResponse<any>) => { cache.set(req.urlWithParams, { key: req.urlWithParams, body: event.body, dateAdded: Date.now(), }); }) ); |
5. Operations of Angular HTTP Interceptor
Angular interceptors allow us to do a lot of different things. Let’s do it step by step:
5.1 Modify HTTP Headers
From HttpInterceptors, we may change HTTP headers.
An instance of HttpHeaders, headers are an element of the Angular class HttpRequest. You may store the headers in a key-value fashion using the headersproperty of the HttpHeaders class, that’s a type of Map.
Here is the header that was supplied to the intercept function by the HttpRequest object:
@Injectable() class AnHttpInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // we can set a new Header req.headers.set({"MyHeader": 789}) // we can modify a Header req.headers.append({"Content-Type": null}) // we can delete a Header req.headers.delete("Content-Type") return next.handle(req); } } |
We changed the request’s HttpRequest code. The recommendation was to keep the req HttpRequest unchanged. This prevents the HttpInterceptors from repeatedly processing the identical request.
Then, we should make some changes to the req by cloning it. To make a duplicate of an existing request, you can use the clone method of the HttpRequest class. After making a replica of the original request using the clone method, we changed the headers and sent it on to the next step in the process.
@Injectable() class AnHttpInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const reqCopy = req.clone() // we can set a new Header reqCopy.headers.set({"MyHeader": 789}) // we can modify a Header reqCopy.headers.append({"Content-Type": null}) // we can delete a Header reqCopy.headers.delete("Content-Type") return next.handle(reqCopy); } } If our Http request was this: { headers: { Content-Type: "application/json" }, body: {...}, url: "", method: "" } |
This is the result of its modification after going via AnHttpInterceptor:
{ headers: { "MyHeader": 789 }, body: {...}, url: "", method: "" } |
This is the data that would be sent to the server.
5.2 Modification of Request Body
Changing the headers property of a HttpRequest is something we’ve already covered.
Additionally, we may alter the request body using HttpInterceptor.
You may add more information to your HttpRequest by using the “body” field and the post method.
The HttpRequest object that is given to the HttpInterceptor allows us to access and alter the body property as needed.
Assume the following is our Http request:
httpClient.post("/api/login", { name: "Aron", password: "xxrT"}) |
This { name: “Aron”, password: “xxrT”} is the body in the Http request.
In our AnHttpInterceptor, we can modify any of the properties:
@Injectable() class AnHttpInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { // remove name cont body = {password: req.body.password } const reqCopy = req.clone({ body }) // modify name to "Sharon" cont body = {...req.body, "name": "Sharon"} const reqCopy = req.clone({ body }) return next.handle(reqCopy); } } |
Now the server will get:
{ url: "/api/login", body: { name: "Sharon", password: "xxrT" } } |
5.3 Cancel The Current Request
By restoring the EMPTY observable, we may also terminate the present request.
The below code sample verifies the user’s login status. In that case, the request will not be sent to the server.
import { EMPTY } from 'rxjs'; intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { if (NotLoggedIn) { return EMPTY; } return next.handle(request); } |
5.4 Set Authentication/Authorization Token
With the HttpInterceptor interface, you may add an authorization header to all of a domain’s HTTP requests.
When we need to secure a server-side API endpoint, we may employ authorization using HttpInterceptors. In order to intercept all HTTP requests, we will build a HttpInterceptor that includes a “Bearer” token in the permission header:
@Injectable() export class AuthInterceptor implements HttpInterceptor { intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { const token = localStorage.getItem("token"); if (token) { const cloned = req.clone( setHeaders: { Authorization: `Bearer ${token}`, }); return next.handle(cloned); } else { return next.handle(req); } } } |
To begin, it retrieves the token from the localStroage instance. The next step is to use the authorization token to change the “Authorization” header parameter to “Bearer” and then duplicate the HTTP request (because HTTP requests are kept immutable). The last step is for it to be transferred through the pipeline.
We’ll check the server’s validation process for this token.
We can verify the request’s authenticity by examining the date and user identity, as well as by extracting the JWT from the Authorization header. All of the routes that require authorization will undergo this authentication process.
Assume we have a Node server that is supported by Express. Using Express, we will build the routes and then install the authentication middleware:
const app = express() app .route("api/refferals") .get(authMidWare, refferalCtrl.getRefferals) |
A protected route called “api/refferals” exists. The authNidWare must validate the Authorization header in the HTTP request before granting access to “refferalCtrl.getRefferals” in order to retrieve the referals list.
To verify the Bearer token, we’ll make use of the auth0/express-jwt library.
npm i express-jwt const app = express() const jwt = require("express-jwt") const authMidWare = jwt({ secret: YOUR_SECRET_KEY_HERE }) app .route("api/refferals") .get(authMidWare, refferalCtrl.getRefferals) |
In order to verify the Bearer token’s authenticity, the Authorization header provides the jwt with a secret key. If the Bearer token is not authenticated or has run its course, the jwt will raise an error. The referals list will be returned after running the refferalCtrl.getRefferals midware, provided that all goes well.
5.5 Mock Backend
There are instances when we need to move quickly, yet the APIs on the server side are not yet prepared to function. With Angular’s interceptor, you can easily build a simulated backend service. Using the interceptor, you can easily generate your own false response data.
Let’s have a look at the interceptor’s steps for making a mock backend.
import { Injectable } from "@angular/core"; import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent, HttpResponse, HttpErrorResponse, } from "@angular/common/http"; import { throwError, of } from "rxjs"; import { catchError, tap, delay } from "rxjs/operators"; const mockUsers = [ {id: 1, name: "John snow", email: "[email protected]"}, {id: 1, name: "Albus snap", email: "[email protected]"}, ]; @Injectable() export class MockBackendInterceptor implements HttpInterceptor { intercept( req: HttpRequest<any>, next: HttpHandler ): import("rxjs").Observable<HttpEvent<any>> { if (req.method === "GET" && req.url.includes("/users") { return of(new HttpResponse({ status: 200, body: mockData })).pipe( delay(1000)); } next.handle(req); } } |
We have defined a mockData constant in the preceding code. Starting on lines 26–28, we are using the URL checker to deliver this mockData as a 200 status code service response.
We are utilizing our mock data service in our app.component.ts file.
ngOnInit() { this.http .get("http://localhost:4200/users") .subscribe(data => console.log(data)); } |
5.6 Caching
To make things run faster, we may store requests and responses from HTTP in a cache. Caching is possible for the GET method of an HTTP request. Take the profile part of your Angular app as an example. After a user makes changes to their profile and hits refresh, the website retrieves the updated profile using a GET request, which is a POST action.
Since the results of the prior GET queries and the present Get request are identical—there was no POST executed in between—the website shouldn’t get the profile from the server again if he refreshes it. On the second GET request, it would be unnecessary and slow down performance to get the response from the server again.
When no modifications were made between calls to GET methods, they are perfect candidates for caching.
For Angular’s GET methods that need caching, HttpInterceptor is a solid option. Let’s examine the code.
@Injectable() class CacheInterceptor implements HttpInterceptor { private cache: Map<HttpRequest, HttpResponse> = new Map() intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{ if(req.method !== "GET") { return next.handle(req) } if(req.headers.get("reset")) { this.cache.delete(req) } const cachedResponse: HttpResponse = this.cache.get(req) if(cachedResponse) { return of(cachedResponse.clone()) }else { return next.handle(req).pipe( do(stateEvent => { if(stateEvent instanceof HttpResponse) { this.cache.set(req, stateEvent.clone()) } }) ).share() } } } |
To begin, there is a cache that stores the HttpRequest and HttpResponse as a key-value pair. If the request’s method is not GET, caching is not performed and the request is forwarded down the chain without caching in the intercept method.
It finds the “reset” key in the headers to see if the cache is to be reset. If it is, we remove the response from the cache and send out a fresh request.
The next step is to get the request’s cached response. It returns the answer from the cache if there is one. In such a case, the server will get a fresh request that has been routed down the pipeline. After the server sends back a response, the do operator retrieves it and stores it in a cached request-response pair. After that, the part or function that triggered the response is reached via the interceptor’s chain.
5.7 Logging or Profiling
Because they can handle the request and response concurrently, Angular HTTP Interceptors can record a entire HTTP operation.
After that, we can record the result with the time it took by obtaining the request and response timings.
const startTime = Date.now(); let ok:string; return next.handle(req).pipe( tap((event: HttpEvent<any>) => { ok = event instanceof HttpResponse ? 'succeeded' : '' }, (error: HttpErrorResponse) => ok = "failed"), // Log when response observable either completes or errors finalize(() => { const endTime = Date.now(); console.group("Response Group:"); console.log("Time:", (endTime - startTime).toFixed(3) + "ms"); console.log("Time:", req.method); const msg = `${req.method} "${req.urlWithParams}" ${ok} in ${(endTime - startTime).toFixed(3)} ms.`; console.log(msg); console.groupEnd(); }) ); |
5.8 Error Handling
Any application would benefit greatly from an API response free of errors when it comes to HTTP calls. However, the ideal approach is to anticipate that they are possible and inevitable and propose a sophisticated solution. A broken view or a large number of errors presented to the end user are the last things anyone wants to see when their request fails. There are numerous possible causes for this.
A sophisticated approach would be to construct an error handler that would catch any HTTP problem and treat it accordingly.
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest, } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { MessageService } from 'primeng/api'; import { Observable, throwError } from 'rxjs'; import { catchError } from 'rxjs/operators'; @Injectable() export class ErrorHandlingInterceptor implements HttpInterceptor { constructor(private messageService: MessageService) {} intercept( request: HttpRequest<any>, next: HttpHandler ): Observable<HttpEvent<any>> { return next.handle(request).pipe( catchError(this.handleError), tap(event => { if (event instanceof HttpResponse) { // Optional: Handle successful responses here } }) ); } private handleError(error: HttpErrorResponse): Observable<any> { if(error.error instanceof ErrorEvent) { this.messageService.add({ severity: 'error', summary: `An error occurred: ${error.message}`, }); } else { console.error( `Backend returned code ${error.status}, ` + `body was: ${error.error}`); } let errorMessage = 'please try again later.'; switch (error.status) { case 400: errorMessage = 'Bad request. Please check your input.'; break; case 401: errorMessage = 'Unauthorized. Please check your credentials.'; break; case 403: errorMessage = 'Forbidden. You don\'t have permission to access this resource.'; break; case 404: errorMessage = 'Not Found. The resource you requested may not exist.'; break; case 409: errorMessage = 'Conflict. The request could not be completed due to a conflict.'; break; case 500: errorMessage = 'Internal Server Error. The server encountered an unexpected condition.'; break; default: // Handle other error codes as needed } return throwError(errorMessage); } } |
Since the code ought to be self-explanatory, not much elaboration is required. Filtering the mistakes is the sole relevant detail to address. Errors when the HTTP response does match the particular error code we handle it.
For what reason is this the case? At the outset, one may execute several interceptors in a chained process. We don’t need to manage 401 errors on this interceptor as the auth interceptor is already handling them.
In this example, an error is simply shown to the user in the form of a toast with the error message; however, this is an ideal spot to style or build personalized alerts depending on particular issues.
5.9 Notifications
There is a vast variety of contexts in which we may display messages here. Every time we receive a 201 status from the server, “OBJECT CREATED” is shown in the sample below.
return next.handle(req).pipe( tap((event: HttpEvent<any>) => { if (event instanceof HttpResponse && event.status === 201) { this.toastr.success("Object created."); } }) ); |
We can display “TYPE CREATED” to verify the object’s type. By enclosing our data in an object and adding a message, we can also display messages with better precision.
6. Can We Have Multiple Interceptors?
Yes, Angular allows you to specify many interceptors, each with its scope. You may create separate interceptors for different tasks; for instance, one might handle errors, another would set the authentication header, and so on.
When registering in @NgModule, use multi:true to take advantage of several angular interceptors.
@NgModule({ providers: [ { provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi:true, provide: HTTP_INTERCEPTORS, useClass: ErrorHandlerInterceptor, multi:true, ... }, ]}) |
7. Conclusion
With the addition of Angular HTTP interceptors to the HTTP client library, Angular 4.3 made life easier for every developer. Possibilities like the ones mentioned and shown above became possible when outgoing requests and incoming answers could be handled in the same location simultaneously.
In the end, we hope this post has been interesting to read and useful to you. All the best.
Comments
Leave a message...