Make API Calls the Right Way in Angular
Best practices for making your API calls

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:
- 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.
- 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. - 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:
- Base URL: https://domain.com/api
- Action:
get-users
- 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:
- Create an API URL that uses the real or the mock API.
- Create an API URL with query strings.
- 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.