15

7+ Ways to Leverage HttpInterceptors in Angular

 4 years ago
source link: https://blog.bitsrc.io/7-ways-to-leverage-httpinterceptors-in-angular-59436611844d
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

7 Ways to Leverage HttpInterceptors in Angular

Useful implementations of Angular’s HttpInterceptor

Ab6RZ3I.jpg!web

What are HttpInterceptors?

According to angular.io :

HTTP Interception is a major feature of @angular/common/http. With interception, you declare interceptors that inspect and transform HTTP requests from your application to the server.

The same interceptors may also inspect and transform the server’s responses on their way back to the application. Multiple interceptors form a forward-and-backward chain of request/response handlers.

HttpInterceptors, as their name suggests, intercept Http requests made in an Angular app. Intercepting means that they catch and channel the Http requests, before passing it on to the webserver.

In Angular, communication with servers is done by injecting the HttpClient service class and call the methods:

  • get
  • post
  • delete
  • options
  • put

based on the action we want on the server.

Tip:as we all know, rewriting code is a recipe for bad code. Use tools like Bit ( Github ) to “harvest” reusable components from your codebase and share them on bit.dev . This way you and your team can easily find them, import them to any project and develop them further if needed. It’s a good way to guarantee high quality, scalable and maintainable code.

uyM3Ejm.jpg Example: searching for shared components in bit.dev

for example:

@Component({
    ...
})
class AppComponent {
    feed$: Observable    constructor(private httpClient: HttpClient) {}    ngOnInit() {
        this.feed$ = this.httpClient.get("/api/feed")
    }
}

This component uses the HttpClient#get method to perform an HTTP GET request to the “/api/feed” endpoint when it is created.

A HttpInterceptor if configured on the app would catch the Http GET request to “/api/feed” and can do anything with it from auth to logging anything at all the developer.

To create an HttpInterceptor in an Agular app, you first create a service class and make the class implement the intercept method of the HttpInterceptor interface.

@Injectable()
class AnHttpInterceptor implemenss HttpInterceptor {
    intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
        return next.handle(req);
    }
}

We have an HttpInterceptor here, AnHttpInterceptor, it does nothing actually, it just passes the request down the interceptor chain.

An HttpInterceptor must implement the intercept method, this is where it receives the Http request in the first parameter req and the next HttpHandler next in the chain. To pass the Http request down the interceptor pipeline it calls the HttpHandler#handle method.

This AnHttpInterceptor won’t intercept any Http requests like this, we have to provide it to the base NgModule providers array:

@NgModule({
    providers: [
        {
            provide: HTTP_INTERCEPTORS,useClass: AnHttpInterceptor, multi: true 
        }
    ]
})

Now, the AnHttpInterceptor will intercept Http requests in our app.

We have a clear understanding of how HttpInterceptors works, let’s look where they can get very useful.

1. Header Modification

We can modify Http headers from HttpInterceptors.

headers is a property in the HttpRequest class in Angular, an object of HttpHeaders. The HttpHeaders class has headers property which is a Map instance for storing the headers in a key-value way.

So from the HttpRequest object passed to the HttpInterceptor intercept method, we can reference the header:

@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 mutated the HttpRequest req. It said that the req HttpRequest should be left immutable. This is to avoid the HttpInterceptors from re-processing the same request multiple times.

So, we need to clone the req and modify it. HttpRequest has a method clone for creating a new copy of the original request. We’ve used the clone method to create a copy of the orig request and modify the headers in the copy and pass the copy down the chain.

@Injectable()
class AnHttpInterceptor implemenss 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: "aplication/json"
    },
    body: {...},
    url: "",
    method: ""
}

after passing through AnHttpInterceptor; it will be modified to this:

{
    headers: {
        "MyHeader": 789
    },
    body: {...},
    url: "",
    method: ""
}

This is what the server would receive.

2. Request body modification

We have seen how to modify the headers property in the req HttpRequest.

We can also through HttpInterceptor modify the request body.

In HttpRequest the body is a property that is used to send additional information to the server via the post method.

So through the HttpRequest object passed to the HttpInterceptor we can refer to the body property and modify it at will.

Let’s say our Http request was this:

httpClient.post("/api/login", { name: "nnamdi", password: "xxxx"})

This { name: “nnamdi”, password: “xxxx”} is the body in the Http requets.

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
        })        // modfiy name to "chidume"
        cont body = {...req.body, "name": "chidume"}        const reqCopy = req.clone({
            body
        })        return next.handle(reqCopy);
    }
}

Now the server will get:

{
    url: "/api/login",
    body: {
        name: "chidume",
        password: "xxxx"
    }
}

3. HttpRequest body

Since we can get the HttpRequest, we can modify any properties in it: URL, method, headers, body, and other request configuration options.

class HttpRequest {
  /**
   * The request body, or `null` if one isn't set.
   *
   * Bodies are not enforced to be immutable, as they can include a reference to any
   * user-defined data type. However, interceptors should take care to preserve
   * idempotence by treating them as such.
   */
  readonly body: T|null = null;  /**
   * Outgoing headers for this request.
   */
  // TODO(issue/24571): remove '!'.
  readonly headers !: HttpHeaders;  /**
   * Whether this request should be made in a way that exposes progress events.
   *
   * Progress events are expensive (change detection runs on each event) and so
   * they should only be requested if the consumer intends to monitor them.
   */
  readonly reportProgress: boolean = false;  /**
   * Whether this request should be sent with outgoing credentials (cookies).
   */
  readonly withCredentials: boolean = false;  /**
   * The expected response type of the server.
   *
   * This is used to parse the response appropriately before returning it to
   * the requestee.
   */
  readonly responseType: 'arraybuffer'|'blob'|'json'|'text' = 'json';  /**
   * The outgoing HTTP request method.
   */
  readonly method: string;  /**
   * Outgoing URL parameters.
   */
  // TODO(issue/24571): remove '!'.
  readonly params !: HttpParams;  /**
   * The outgoing URL with all URL parameters set.
   */
  readonly urlWithParams: string;
}

With the object of the HttpRequest passed to the intercept method of the HttpInterceptor, we can modify any of the properties in the HttpRequest class.

4. Authentication/Authorization

HttpInterceptor can be used to set an authorization header to the HTTP requests going to a particular domain.

Authorization via HttpInterceptors is useful when we want to protect an API endpoint in the server. We will create an HttpInterceptor, it will append an authorization header with a “Bearer” token to every HTTP requests:

@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({
        headers: req.headers.set("Authorization", "Bearer " + token)
      });
      return next.handle(cloned);
    }
    else {
      return next.handle(req);
    }
  }
}

First, it gets the token from the localStroage. Then, it clones the Http request(because Http requests are kept immutable), and sets the “Authorization” header on the request with a value “Bearer “ concated with the token. then, finally, it is passed down the pipeline.

Let’s see how to validate this token on the server.

In order to authenticate the request, we are going to have to extract the JWT from the Authorization header, and check the timestamp and the user identifier. This authentication will be applied to the routes that we want authorization for.

Let’s say we have an Express-backed Node server. We will create the routes using Express and add the authentication middleware on it:

const app = express()app
  .route("api/refferals")
  .get(authMidWare, refferalCtrl.getRefferals)

The route “api/refferals” is protected. Before refferalCtrl.getRefferals is called to return the list of refferals, the authNidWare must authenticate the Authorization header in the HTTP request before passing access to "refferalCtrl.getRefferals".

We will use the auth0/express-jwt library to validate the Bearer token.

npm i express-jwtconst app = express()
const jwt = require("express-jwt")const authMidWare = jwt({
  secret: YOUR_SECRET_KEY_HERE
})app
  .route("api/refferals")
  .get(authMidWare, refferalCtrl.getRefferals)

The jwt is passed a secret key, which it will use to authenticate the Bearer token in the Authorization header. The jwt will throw an error if the Bearer token is not correctly signed and if the token has expired. If all passes the refferalCtrl.getRefferals midware will be run and the refferals list will be returned.

5. Backend Mock

There are times when we want to put up a fast server for testing out our Angular app behavior. We will find even making the simplest server from Nodejs or any other lang framework very tedious. We just want simple stuff that will just serve us JSON objects very fast.

HttpInterceptors will become very useful in this case. In this case, instead of passing down the Http request req down the chain via HttpHandler#handle , we will return and HttpResponse with the fake data we want.

Let’s say we have a component that requests for the list of users from the server:

@Component({
    ...
})
class AppComponent {
    users$: Observable    ngOnInit() {
        this.users$ = this.httpClient.get("/api/users")
    }
}

We will use HttpInterceptor to intercept the Http call to “/api/users” and return an Observable that contains an array of users:

const usersData = {
    "users": [
        {
            "name": "chidume nnamdi",
            "age": 26
        },
        {
            "name": "chisom",
            "age": 46
        },
        {
            "name": "elvis",
            "age": 21
        },
        {
            "name": "osy mattew",
            "age": 21
        },
        {
            "name": "valentine",
            "age": 21
        },
    ]
}@Injectable()
class BackendInterceptor implemnets HttpInterceptor {
 constructor(private injector: Injector) {} intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
     return of(new HttpResponse({
         status: 200, 
         body: usersData 
     }));
 }
}

Instead of HttpHandler#handle, we created an Observable of a new HttpResponse passing in the fake user array usersData, with a response status of 200 and returned the Observable, this will not hit any real server.

We can modify the interceptor to check for the URL path before knowing what to return.

...@Injectable()
class BackendInterceptor implemnets HttpInterceptor {
 constructor(private injector: Injector) {} intercept(request: HttpRequest, next: HttpHandler): Observable<HttpEvent<any>> {
     if(request.url.includes("/api/users"))
        return of(new HttpResponse({
            status: 200, 
            body: usersData 
        }));
     next.handle(request)
 }
}

We check if the url in the request object has the “/api/users” endpoint in it so we know to return the fake users array. If not we pass the request down the chain.

6. Caching

We can cache Http requests and responses to improve performance.

The GET method of an Http request can be cached.

Let’s say for example, in the profile section of your app. A user can update his profile that performs a POST operation after he updates his profile and refreshes the page, the page performs GET request to get the new profile.

If he refreshes the page again, the page shouldn’t get the profile from the server again, because the previous GET requests would get the same result as the current Get request, no POST was performed inbetween them. Getting the result from the server again on the second GET request would be not necessary and would hamper performance.

GET methods are ideal to be cached when no modifying requests were made in between them.

HttpInterceptor is a good choice of caching GET methods in Angular. Let’s look at 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()
    }
  }    
}

First, we have a cache where the HttpRequest and HttpResponse are stored in a key-value pair. In the intercept method, there check for GET method, if the method is not GET, no caching is done the request is passed down the chain.

It checks if the cache is to be reset, by checking for the “reset” key in the headers . If present, the cached response is deleted and a new request is made.

Next, the cached response of the request is gotten from the cache. If there is a response in the cache, the response is returned. If not, a new request is piped down the chain eventually to the server. The server returns a response, the response is gotten via the do operator, which is cached against the request in a request-response pair in the cache. Then, the response is channeled through the chain of the interceptor to the component/service that called it.

7. Logging

We can log the HttpRequest and the HttpResponse from HttpInterceptor.

We can log the time it took from the request to the response, we log the HttpRequest and HttpResponse stats, just any information you want, it is up to you to know what you want to log.

Let’s see how we can log the time from the request to the response.

@Injectable()
class LoggingInterceptor implements HttpInterceptor {  
  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>>{      const started = Date.now()    return next.handle(req).pipe(
        finalize(() => {
            const elapsed = Date.now() - started;
            log(`URL: ${req.url} Method: ${req.method} Time took: ${elapsed} ms`)  
        }))
    }
  }    
}

This will log the url of the request, the method and the time it took to reach the server and get a response.

Conclusion

Useful things we can do with HttpInterceptors are endless. Here I listed a few of them, you can go on and find ways in which they can be very helpful in your Angular app.

Don’t forget to pen down your suggestions, comments, notes, corrections below or you can DM or email them.

Thanks!!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK