DEV Community

Leonardo Victor
Leonardo Victor

Posted on Edited on

Angular: Consuming multiple APIS with Angular HTTPClient in a beautiful way using interceptors

Use of multiple back-ends, with different headers, in Angular applications could be boring and look a mess, but what can we do?

Instead of making imports and references to the environment file always when necessary, I usually use HttpInterptor to add the correct back-end base URL and set headers based on the context. If you are having problems in treat different environment in server-render, it also could be the solution.

Basic, the idea is use a string prefix in the URL as “@api-x” to identify the back-end base url on each HTTP request.

this.http.get('@api-x/specific_endpoint')
Enter fullscreen mode Exit fullscreen mode

How do it work? Follow the guide bellow.

1- First, you need to create a Interceptor using HttpInterceptor:

import { Injectable } from ‘@angular/core’;
import { HttpInterceptor, HttpHandler, HttpRequest, HttpEvent } from ‘@angular/common/http’;
import { Observable } from ‘rxjs’;
import { environment } from ‘environments/environment’;
@Injectable()
export class ApiInterceptor implements HttpInterceptor {
 constructor() { }
 intercept(
   req: HttpRequest<any>,
   next: HttpHandler
 ): Observable<HttpEvent<any>> {
   // Get the request url
   let requestUrl = req.url;
   // if the request URL have the string prefix, 
   // then make the replace by the correct url 
   if (requestUrl.indexOf(‘@api-x’) !== -1) {
     requestUrl = requestUrl.replace(‘@api-x’, environment.api);
   }
   // clone the http request
   req = req.clone({
     url: requestUrl
   });
   // move to next HttpClient request life cycle
   return next.handle(req);
 }
}
Enter fullscreen mode Exit fullscreen mode

Basic here, we’re creating a interceptor that receive the request and replace the string prefix “api-x” in the requested URL by the correct URL defined in the environment file.

2- Add the created interceptor in some application module: register in providers array.

...
imports: [
 // import the http client module
 HttpClientModule,
 ...
],
providers: [
 // register the interceptor created
 {
   provide: HTTP_INTERCEPTORS,
   useClass: ApiInterceptor,
   multi: true
 },
 …
]
...
Enter fullscreen mode Exit fullscreen mode

I recommend register it in AppModule or SharedModule, if the application have the SharedModule. But attention, we need to provide at same module that the HttpClientModule are being imported or in a child module. Why do you need it? In resume it is because how the dependency injection works in Angular.

3- Import the http client and create the request: done.

...
constructor(
  // import the dependency with the constructor
  private http: HttpClient
) {}
getNews() {
  return this.http.get(‘@api-x/specific_endpoint_to_get_news');
}
...
Enter fullscreen mode Exit fullscreen mode

Now you can make the request without difficulty just using the prefix @api-x in the request URL parameter.
But if we need more base backed URLs, what we can do?

4- Add more prefix.
Modify the implementation of ApiInterceptor.

...
let requestUrl = req.url;
if (requestUrl.indexOf(‘@api-x’) !== -1) {
  requestUrl = requestUrl.replace(‘@api-x’, environment.api);
}
//added: a new prefix "@api-y"
if (requestUrl.indexOf(‘@api-y’) !== -1) {
  requestUrl = requestUrl.replace(‘@api-y’, environment.otherBackEnd);
}
...
Enter fullscreen mode Exit fullscreen mode

Add the new request to new api.

...
constructor(
  private http: HttpClient
) {}
getNews() {
  return this.http.get('@api-x/specific_endpoint_to_get_news');
}

getDocuments() {
  return this.http.get('@api-y/specific_endpoint_to_get_documents');
}

...
Enter fullscreen mode Exit fullscreen mode

Ok, now we can use multiple APIs in a easy way, but if we need to set headers, what could we do?
5- Set headers based on the context.
Modify the ApiInterceptor to accept it:

...
// added: capture the token - could be a service call here
const tokenApiX = localStorage.getItem(‘tokenApiX’);
let requestUrl = req.url;
if (requestUrl.indexOf(‘@api-x’) !== -1) {
   requestUrl = requestUrl.replace(‘@api-x’, environment.api);
   // added: add the header 
   // only to request with url that contain the prefix @api-x
   req = req.clone({setHeaders: {
     Content-Type: 'application/json',
     Access-Control-Allow-Origin: '*',
     Authorization: "Bearer " + tokenApiX 
   }});
}
if (requestUrl.indexOf(‘@api-y’) !== -1) {
  requestUrl = requestUrl.replace(‘@api-y’, environment.otherBackEnd);
}
...
Enter fullscreen mode Exit fullscreen mode

How we have the control of the workflow for each back-end base url, we can just apply the headers that the application need just to a specific string prefix or to both. We can do everything what we want. It is powerful.
Ok, but how it could help us in server render context?

6- Handle different back-end base URLs depending of runtime environment: browser or node (server).
Modify the interceptor.

import { PLATFORM_ID, Inject } from ‘@angular/core’;
import { isPlatformBrowser} from ‘@angular/common’;
…
export class ApiInterceptor implements HttpInterceptor {
 constructor(@Inject(PLATFORM_ID) platformId: string) { }
 intercept(
…
 if (requestUrl.indexOf(‘@api-x’) !== -1) {
   // added: verification of browser context
   if (isPlatformBrowser(platformId);) {
     //this is only executed on the browser
     requestUrl = requestUrl.replace(‘@api-x’, environment.apiExternal);
   } else {
     //this is only executed on the server
     requestUrl = requestUrl.replace(‘@api-x’, environment.apiInternal);
   }
}
…
Enter fullscreen mode Exit fullscreen mode

Now we can use, with same code of http request, diferents APIS address based on the runtime context, without a lot of modification.
We also could improve the API interceptor implementation code, make a refactoring, but this is subject to another article.

Top comments (3)

Collapse
 
henksteenbroek profile image
Henksteenbroek •

Hey Leonardo,

Thanks for the tutorial. Just want to point out that you use 'urlValue' instead of 'requestUrl' in some of the code samples.

Collapse
 
leonardovff profile image
Leonardo Victor •

Thank you for the feedback, I fixed it.

Collapse
 
saulodias profile image
Saulo Dias • • Edited

Very beautiful indeed. Thank you, Leo!