DEV Community

Leonardo Victor
Leonardo Victor

Posted on • Updated 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.

Discussion (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 Author

Thank you for the feedback, I fixed it.

Collapse
saulodias profile image
Saulo Dias • Edited on

Very beautiful indeed. Thank you, Leo!