Angular: Making API Calls the right way

Angular making API calls

The Angular framework is particularly popular for developing Enterprise applications. If you’re working on an Angular application, I am almost certain you’ll have to make some API calls. In this article, we’ll try to go through some of the common mistakes developers make while making API calls from an Angular application and how to improve the code to avoid some bugs, achieve more with fewer efforts, and making the maintenance easier.

The simplest way to make an API call from an Angular application would be to inject HttpClient in a component and invoke one for the methods (get(), post(), put(), etc.) to make an API call. This may look something like this.

this.httpClient.get('https://api.example.com/users/' + this.id);

This is not ideal for multiple reasons:

  1. You’re hard-coding the API URL directly in the component, and if you need to change the API URL, you have to update it in every component.
  2. You’re using Angular’s HttpClientdirectly in the component. If Angular replaces this module with another module, you must change it in every component. Remember what happened with HttpModule, yes, it got replaced with HttpClientModule.
  3. The path variable is not encoded. So if a character isn’t a valid URL character, the API call will fail.

So, How to deal with this?

You’ll realize as your application grows in size that the best way to keep things in control is:

  • Keep your components simple and does one specific, well-defined operation
  • Make sure your components focus on the presentation layer
  • Off-load any other logic except presentation logic to services
  • Divide your application code in CoreModule, SharedModule and Feature Modules.

Configure your REST API Domain/Host

In Enterprise software development domain particularly, you’d likely maintain one or more test environments (Test, Acceptance, etc.) where you’d want to deploy your application and test it before it can be moved to Production. As you’d deploy your Angular application in these environments, you’d also expect different versions of APIs deployed in these environments. Angular comes with built-in supports for multiple environments specific configurations.

Be default, when you create a new Angular application, you’d notice environment.ts and environment.prod.ts files created under src/environments directory. These files are supposed to hold environment-specific configurations and API Host/Domain, since it will be different for each of these environments, can be kept here.

So your environment.acc.ts may look something like this

export const environment = {
  API_HOST: 'https://api.acc.example.com'
};

And environment.prod.ts may look like this:

export const environment = {
  production: true,
  API_HOST: 'https://api.example.com'
};

This way we can use the environment.prod.ts for Production build and environment.acc.ts for Acceptance build.

Configure REST endpoints:

Sincce the endpoints of these APIs can be same on each of these environments, we can only define them once. If that is not the case for you, you can define the endpoints as well in the environment.*.ts files.

To configure the endpoints, create a constants file called src/app/config/constants.ts. You can use this file to configure the REST endpoints which may look something like this:

import { Injectable } from '@angular/core';

export const Constants = {
  REST_API:{
    users: '/api/users',
    books: '/api/books'
  }
};

You can also make this a Class or a Service, I prefer to keep it simple since declaring it as const serves the purpose just as well.

The only time you’ll ever touch this REST_API property again, is if you have to add some other endpoints or update existing ones. And if something changes on the backend, you’ll only have to change here and you’re good to go. Neat, right!

Create an http.service.ts File

I generally like to maintain one generic service to make all API calls for my Angular applications. This service will simply wrap the functionality of Angular HttpClient, so that if in future I have to switch to some other module for making REST calls, I only have to change this file.

So, inside src/app/core/services, create an http.service.ts file.

// Angular Modules
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

@Injectable({
  providedIn: 'root'
})
export class HttpService {
  constructor(private http: HttpClient) { }

  public get(url: string, options?: any) {
    return this.http.get(url, options);
  }

  public post(url: string, data: any, options?: any) {
    return this.http.post(url, data, options);
  }

  public put(url: string, data: any, options?: any) {
    return this.http.put(url, data, options);
  }

  public delete(url: string, options?: any) {
    return this.http.delete(url, options);
  }
}

This service has one simple job, to provide abstraction for the HttpClient.

The HttpModulebecame deprecated with the release of Angular 4.3 and the new HttpClientModule became the default module for making API calls. If something like this happens again in the future, you just saved yourself some headaches with this simple abstraction service. You won’t need to change the use of the module in every component but only in this file. The upgrade will be easier.

Serialize your query params

This is particularly for GET calls when you’d want to send some query params in the URL when you make an API call. to make sure that your parameters are serialized properly, you can pass you query params object through this function:

  /**
   * Convert an object or query params into query string
   * @param obj object containing query params
   */
  serializeObject(obj: {}) {
    let finalString = '';
    if (Object.keys(obj).length > 0) {
      Object.keys(obj).forEach((key: string) => {
        finalString += finalString ? '&' : '?';
        finalString += key + '=' + encodeURIComponent(obj[key].toString());
      });
    }
    return finalString;
  }

This function accepts an object as key-value pairs of query params you want to send and returns a properly encoded query-string.
e.g., Input: { id: 3, type: ‘Admin’} would spit out ?id=3&type=Admin.

Create a feature service

I prefer to create separate services for each feature/resource. So, in this case, I’d create a UserService, which may look something like this:

@Injectable({
  providedIn: 'root'
})
export class UserService {
  API_host: environment.API_HOST;
  USERS_PATH: Constants.REST_API.users;

  constructor(private httpService: HttpService) { }

  public listUsers(options?: any) {
    return this.httpService.get(this.API_host + this.USERS_PATH, options);
  }

  public createUser(data: any, options?: any) {
    return this.httpService.post(this.API_host + this.USERS_PATH, data, options);
  }

  public updateUser(data: any, options?: any) {
    return this.httpService.put(this.API_host + this.USERS_PATH, data, options);
  }

  public deleteUser(options?: any) {
    return this.httpService.delete(this.API_host + this.USERS_PATH, options);
  }
}

Update the Component

Now, let’s go to a component and use them all together.

  constructor(
    // Application Services
    private userService: UserService,
  ) {
  }

  ngOnInit() {
    this.userService.listUsers().subscribe((users) => {
      console.log('Users loaded.'))
  });

So, with this setup,

  • We make sure that the API_HOST is always environment-specific
  • API endpoints can be configured in a constants file or Class
  • A wrapper HttpService protects your application against any breaking changes to the Angular HttpClient.
  • A feature-specific service can be used to handle some extra processing on top of the response given back by HttpService or before the request is handed over to the HttpService.

And that is how you create an Enterprise scale setup for API calls within an Angular application. Of course, you don’t have t wait until your application is gigantic bloat, you can start from the beginning and sit and relax as it makes your life easier in the future.

I hope this helps you with your future or current Angular projects. Let me know how you feel about this and if you have any suggestions, use the comments section below. Cheers!

Be sure to follow us on the social media to get notified about the latest posts as soon as they’re published

Leave a comment

Your email address will not be published. Required fields are marked *