Make API Calls the Right Way in Angular

Best practices for making your API calls

George Roubie
Better Programming
Published in
5 min readJan 6, 2020

--

If you’re creating an Angular application, one of the things you’ll need to do is make an API call. I’ll show you how to do it the right way, avoiding bugs or repeating yourself and making it easier to upgrade to a new version of Angular.

A common mistake of an API call inside a component is this:

this.httpClient.get('https://www.domain.com/api/data/' + this.id);

This is wrong for three reasons:

  1. You’re using the API URL directly in the component, and if you need to change the API URL, you must change it in every component.
  2. You’re using Angular’s httpClient directly in the component, and if Angular replaces this module with another module, you must change it in every component.
  3. The path variable is not encoded. So if a character isn’t a valid URL character, the API call will fail.

First of all, you must know what a CoreModule and a SharedModule are. If you don’t know, please read this.

Getting Started

Inside src/app, create a folder called config with a constants.ts file inside.

// Angular Modules
import { Injectable } from '@angular/core';
@Injectable()
export class Constants {
public readonly API_ENDPOINT: string = 'domain/api';
public readonly API_MOCK_ENDPOINT: string = 'mock-domain/api';
}

Import this file into the CoreModule.

We have two variables: one for the actual API endpoint and one for mock — but why?

Most of the time, the API models are completed but the actual call isn’t implemented. To not wait for an API call to be implemented, you can create a dummy call from, for example, JsonGenerator and use this in your application.

Now if the domain of the API is changed, you only need to change the above file and you’re ready.

Create an api-http.service.ts File

Inside src/app/core/services, create an api-http.service.ts file.

// Angular Modules
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable()
export class ApiHttpService {
constructor(
// Angular Modules
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);
}
}

Import this file into the CoreModule.

In Angular 4.3, the HttpModule became legacy, and the new HttpClientModule became the default module for making API calls. If something like this happens again, you won’t need to change the use of the module in every component but only in this file. The upgrade will be easier.

Creating the Other Requisite Files

Inside src/app/shared/classes, create a query-string-parameters.ts file.

export class QueryStringParameters {
private paramsAndValues: string[];
constructor() {
this.paramsAndValues = [];
}
public push(key: string, value: Object): void {
value = encodeURIComponent(value.toString());
this.paramsAndValues.push([key, value].join('='));
}
public toString = (): string => this.paramsAndValues.join('&');
}

This class gets a query string key-value set and encodes the value. It also returns all the query string key-value sets in a string with the & character:
e.g., id=3&type=customer.

Inside src/app/shared/classes, create an url-builder.ts file.

// Application Classes
import { QueryStringParameters } from './query-string-parameters';
export class UrlBuilder {
public url: string;
public queryString: QueryStringParameters;
constructor(
private baseUrl: string,
private action: string,
queryString?: QueryStringParameters
) {
this.url = [baseUrl, action].join('/');
this.queryString = queryString || new QueryStringParameters();
}
public toString(): string {
const qs: string = this.queryString ?
this.queryString.toString() : '';
return qs ? `${this.url}?${qs}` : this.url;
}
}

This class uses the QueryStringParameters class to generate the final API URL:
e.g., https://domain.com/api/get-user?id=3&type=customer.

The above URL is a combination of three parts:

  1. Base URL: https://domain.com/api
  2. Action: get-users
  3. Query strings: id=3&type=customer

The URL builder takes these things as parameters to create the final API URL.

Inside src/app/core/services, create an api-endpoints.service.ts file.

// Angular Modules
import { Injectable } from '@angular/core';
// Application Classes
import { UrlBuilder } from '../../shared/classes/url-builder';
import { QueryStringParameters } from '../../shared/classes/query-string-parameters';
// Application Constants
import { Constants } from 'src/app/config/constants';
@Injectable()
export class ApiEndpointsService {
constructor(
// Application Constants
private constants: Constants
) { }
/* #region URL CREATOR */
// URL
private createUrl(
action: string,
isMockAPI: boolean = false
): string {
const urlBuilder: UrlBuilder = new UrlBuilder(
isMockAPI ? this.constants.API_MOCK_ENDPOINT :
this.constants.API_ENDPOINT,
action
);
return urlBuilder.toString();
}
// URL WITH QUERY PARAMS
private createUrlWithQueryParameters(
action: string,
queryStringHandler?:
(queryStringParameters: QueryStringParameters) => void
): string {
const urlBuilder: UrlBuilder = new UrlBuilder(
this.constants.API_ENDPOINT,
action
);
// Push extra query string params
if (queryStringHandler) {
queryStringHandler(urlBuilder.queryString);
}
return urlBuilder.toString();
}

// URL WITH PATH VARIABLES
private createUrlWithPathVariables(
action: string,
pathVariables: any[] = []
): string {
let encodedPathVariablesUrl: string = '';
// Push extra path variables
for (const pathVariable of pathVariables) {
if (pathVariable !== null) {
encodedPathVariablesUrl +=
`/${encodeURIComponent(pathVariable.toString())}`;
}
}
const urlBuilder: UrlBuilder = new UrlBuilder(
this.constants.API_ENDPOINT,
`${action}${encodedPathVariablesUrl}`
);
return urlBuilder.toString();
}
/* #endregion */
}

Import this file in the CoreModule.

This service can:

  1. Create an API URL that uses the real or the mock API.
  2. Create an API URL with query strings.
  3. Create an API URL with path variables.

All the API URLs will be provided from this service. Let’s create some URLs inside the service.

public getNewsEndpoint(): string {
return this.createUrl('news');
}

This method will return:
https://domain.com/api/news

public getNewsEndpoint(): string {
return this.createUrl('news', true);
}

This method will return:
https://mock-domain.com/api/news

public getProductListByCountryAndPostalCodeEndpoint(
countryCode: string,
postalCode: string
): string {
return this.createUrlWithQueryParameters(
'productlist',
(qs: QueryStringParameters) => {
qs.push('countryCode', countryCode);
qs.push('postalCode', postalCode);
}
);
}

This method will return:
https://domain.com/api/productlist?countrycode=en&postalcode=12345

public getDataByIdAndCodeEndpoint(
id: string,
code: number
): string {
return this.createUrlWithPathVariables('data', [id, code]);
}

This method will return:
https://domain.com/api/data/12/67

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

constructor(
// Application Services
private apiHttpService: ApiHttpService,
private apiEndpointsService: ApiEndpointsService
) {
ngOnInit() {
this.apiHttpService
.get(this.apiEndpointsService.getNewsEndpoint())
.subscribe(() => {
console.log('News loaded'))
});
}

All of the above are required for a large-scale application, but even for a small one, they’re useful.

Resources

The code for this piece can be found on GitHub here.

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

George Roubie
George Roubie

Written by George Roubie

Experienced Software Engineer with more than 10 years of experience, specialized in Front-End Web technologies, with strong foundations in programming.

Responses (15)

Write a response