No web application is complete without server-side API interactions.
Web pages get data from remote services and present it on the page. These pages communicate with server-side API using the HTTP protocol. Browsers traditionally support XMLHttpRequests (from the days of Ajax). The latest advent is the fetch() API that most of the newer browsers support.
Angular applications need an easy way to interact with these services over HTTP for data retrieval and updation. For this purpose, Angular provides the HttpClient service.
HTTP client in Angular - Getting Started
Import HttpClientModule
To use any service, you need to import the respective module. For HttpClient you can get started by importing the HttpClientModule. You may import it into any Angular module utilizing the HttpClient. In the sample below, we will import it directly to the root AppModule. For brevity, the snippet shows only lines of code required for understanding the concept.
import { HttpClientModule } from '@angular/common/http'
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Prepare to use HttpClient service to make HTTP calls
We can import HttpClient in a Component and start making HTTP calls to retrieve data.
However, this is not a good practice since Data Access gets complex as the application grows.
It is preferable to separate it into an Angular service of its own which will help with separation of concerns.
Let’s create a new service in the sample app and call it DataAccessService. If you are using Angular CLI, run the following command to create a service.
ng g service services/DataAccess
Notes
- ng is used to invoke Angular CLI.
- We use the option g for generating an artifact
- Parameter service to indicate that the artifact we are creating is an Angular service
- For a clean folder structure, we are creating the new service in a folder services. Hence services/<service name>
- DataAccess is the service name we chose.
Use HttpClient to make HTTP calls
In the newly created DataAccessService, import HttpClient from @angular/common/http.
Look at the following code snippet. Notice the service is decorated with injectable. It injects the HttpClient service. The HttpClient object is used to make HTTP GET call.
Observe the function getTravellers:
--- data-access.service.ts ---
import { HttpClient } from '@angular/common/http';
@Injectable()
export class DataAccessService {
constructor(private client: HttpClient) { }
--- data-access.service.ts ---
getTravellers(){
this.client.get(`${DATA_ACCESS_PREFIX}/traveller.json`)
.subscribe( (data) => console.log(JSON.stringify(data)));
}
Notice the get function returns an observable. The subscribe function receives and prints JSON response from a server-side API. Figure-1 shows the console output
Figure1 – JSON response of the API
The response includes the travellers array each containing the traveller object. Imagine parsing this information to create traveller objects.
--- data-access.service.ts ---
this.client.get(`${DATA_ACCESS_PREFIX}/traveller.json`)
.subscribe( (data) => {
let travellers: Array<any> = data && data["travellers"];
console.log(travellers && travellers.map((traveller) => ({
'firstName': traveller['firstName'] || "",
'lastName': traveller['lastName'] || "",
'city': traveller['city'] || "",
'country': traveller['country'] || "",
'age': traveller['age'] || -1
})));
});
- We create an array object named travellers, from the response structure.
- We perform null checks.
- Transform each object to the expected structure.
- Notice traveller object’s field names are not available (while coding in the IDE). That’s because it’s of type Object. Hence we dynamically provide the expected field to be returned.
Explicitly type response object
Considering we are using TypeScript, the above code can be simplified.
Create an interface for Travellers object expected to be returned from the service.
--- traveller.ts ---
export interface Traveller{
firstName: string;
lastName: string;
city: string;
country: string;
age: number;
}
export interface Travellers{
travellers: Array<Traveller>
}
On calling get function, specify the type as a generics parameter. Refer to the highlighted code in the following snippet.
--- data-access.service.ts ---
this.client.get<Travellers>(`${DATA_ACCESS_PREFIX}/traveller.json`)
.subscribe( (data) => {
data.travellers.map(traveller => console.log(`Hello ${traveller.lastName}, ${traveller.firstName}`));
});
Notice the subscribe function can access travellers and other objects on the data directly.
Considering the returned object type is predefined, no string notation is to be followed. Also, typed objects allow IDEs to show better intellisense for the developer, as seen in figure-2.
Figure 2 – IntelliSense on the typed object
Error Handling
While accessing the server-side API, HTTP errors can occur. And when they do, they need to be gracefully handled in the Angular application. May be, show a friendly message to the user.
On the observable returned by the service, so far we have used only the success handler. If we continue to use subscribe in the data access service that we created, we may use the error handler. Later in this section, I will describe another way to handle HTTP response, without having to write subscribe in the service we created. Consider the following snippet.
--- data-access.service.ts ---
this.client.get<Travellers>(`${DATA_ACCESS_PREFIX}/dummy.json`)
.subscribe(
(data) => {
data.travellers.map(traveller => console.log(`Hello ${traveller.lastName}, ${traveller.firstName}`));
},
error => console.log(error)
);
Just so that we invoke the error handler, let’s modify the URL to a non-existent dummy.json.
It produces a 404 error.
In the figure-3 below, notice the error response object type returned by HttpClient object is the HttpErrorResponse object, which has complete information about the network/HTTP error occurred.
Figure 3 – Details of HttpErrorResponse object
More often than not, we do not invoke subscribe function in the Angular service. It abstracts server-side API invocation for components and returns objects returned by the service or the error response.
Let’s modify the service function, getTravellers to return the Observable<Travellers>. Consider the following snippet:
--- data-access.service.ts ---
getTravellers(){
return this.client.get<Travellers>(`${DATA_ACCESS_PREFIX}/traveller.js`);
}
A problem with this code is that the errors need to be handled in a function invoking getTravellers, which could be the component.
However, when we created the service, the idea was to abstract all the code getting data from a server-side API. This includes any errors occurring in the process.
We solve this problem by piping the response to another handler, which will manage the error response.
Observe the following code,
--- data-access.service.ts ---
getTravellers(): Observable<Travellers>{
return this.client.get<Travellers>(`${DATA_ACCESS_PREFIX}/travellers.json`)
.pipe(
catchError( (error: HttpErrorResponse) => {
return new ErrorObservable(`Error retreiving travellers data. ${error.statusText || 'Unknown'} `);
}));
}
catchError is used to handle any errors in an observable sequence. Notice, in this example, we are passing an error of type HttpErrorResponse because the HttpClient could only return an error of this type. It would do so irrespective of the error occurring at the HTTP network level or within the browser, in the observable sequence.
Note: Now that we are returning observable from getTravellers, the HTTP call is not invoked till a subscriber is available. Just calling the getTravellers function wouldn’t invoke the get call (to remote API). The Component will have to have a subscribe function (see snippet 8) or use it in the template (see snippet 9).
--- traveller-list.component.ts ---
ngOnInit() {
this.dataAccess.getTravellers()
.subscribe(
travellers => {
this.travellers=travellers.travellers;
console.log(travellers);
},
error => this.messages.push(error) // You may show error message on the template
);
}
------ traveller-list.component.html -----
<div *ngFor="let msg of messages">
<div> {{ msg }}</div>
</div>
Snippet 8
Notice the snippet 8 adds the errors to an array called messages. It is a field on the component class. Hence the messages can be shown to the user.
Refer to the HTML template part of the snippet for showing messages to the user.
--- traveller-list.component.html ---
<div *ngFor="let traveller of travellers | async">
<div> {{traveller.lastName}}, {{traveller.firstName}} is {{traveller.age}} years old.</div>
<div> City: {{traveller.city}}. Country: {{traveller.country}}</div>
<br />
</div>
Snippet 9
So far we have explored retrieving traveller data from a remote server API. Many applications need more actions namely, create, update and delete. A standard REST API would have HTTP GET call for data retrieval. That’s the reason we used Http Client object’s get method so far. REST standard maps
a) HTTP’s POST for create
b) PUT or PATCH for update and
c) DELETE for delete actions.
HTTP Client has methods that are named exactly the same - post, put/patch and delete for respective actions.
In the following section, let’s consider create, update and delete actions on Traveller data.
Create a new traveller by using POST action
For creating a new traveller record, HTTP Post action is used. Consider the following code snippet that calls post method on HTTP client object.
--- data-access.service.ts ---
createTraveller(traveller: Traveller){
return this.client.post(`${DATA_ACCESS_PREFIX}`, traveller);
}
A new traveller object is passed to the post function. Notice the second parameter, traveller. In the HTTP Post call, the traveller object is passed as request payload. Refer to Figure 4 for network information gathered on Google Chrome (F12). The post method returns an observable, which when invoked, will execute the post on the network.
Figure 4 – Details of POST API call
In the sample, the createTraveller method is called from the component. We may have a form to be filled in by the user before clicking the create button. It will instantiate an object of the type Traveller for the data access service method createTraveller and pass it in as a parameter. Check out the following code snippet.
--- traveller-form.component.ts ---
createTraveller(){
let traveller:Traveller = {
id: this.id,
firstName: this.firstName,
lastName: this.lastName,
age: this.age,
city: this.city,
country: this.country
};
// uses instance of data access service to call createTraveller function
this.dataAccess.createTraveller(traveller)
.subscribe(
success => alert("Done"),
error => alert(error)
);
}
--- traveller-form.component.html ---
<div>
<!-- form fields below -->
<input type="text" [(ngModel)]="id" placeholder="Id" />
<input type="text" [(ngModel)]="firstName" placeholder="First Name" />
<input type="text" [(ngModel)]="lastName" placeholder="Last Name" />
<input type="text" [(ngModel)]="age" placeholder="age" />
<input type="text" [(ngModel)]="city" placeholder="City" />
<input type="text" [(ngModel)]="country" placeholder="Country" />
<!-- A button that invokes create action -->
<button (click)="createTraveller()">Create New Traveller</button>
</div>
Using REST API on HTTP functions on the basis of ID. Whether it is for the API to locate an entity uniquely or for the database, the ID field here plays an important role on the object.
Update Traveller Record by using HTTP PUT or PATCH
For editing a record, we will use the put method on the HTTP Client object. It is very similar to post except that we are editing an pre-existing record. Refer to the code snippet below.
In the data access service, call the put method on the HTTP Client object. It accepts the traveller object as a parameter which will be the request payload on HTTP PUT call on the network. However, notice the URL, as we pass ID of the record to be updated. The server-side API implementation locates the API by this id and updates it in the database (or performs an equivalent action)
--- data-access.service.ts ---
updateTraveller(traveller: Traveller, id){
return this.client.put(`${DATA_ACCESS_PREFIX}/${id}`, traveller);
}
Most server-side services (varies by implementation) update the whole traveller record with the new payload. If a non-required field is missing in the payload, and if the value already exists, the old value will be lost. Hence it’s preferable to create the whole object, irrespective of whether the user modified a field or not.
Note: Going by RESTful API standards, patch HTTP action is used for updating the fields instead of replacing the whole entity. On HTTP Client object, you may use patch instead of put. The UI application implementation doesn’t vary. More often than not, it’s server-side API that might have both PUT and PATCH available or just one of them. Review, the code snippet below figure-5 for patch implementation.
Figure 5 – Details of PATCH API call
--- data-access.service.ts ---
updateTraveller(traveller: Traveller, id){
return this.client.patch(`${DATA_ACCESS_PREFIX}/${id}`, traveller);
}
For update/PUT/PATCH, the component creates an object of traveller with the updated information. It is taken from the form fields in which the user has keyed-in values. The component calls updateTraveller function on data access service. On invoking the subscribe for HttpClient object’s put or patch, it will make the HTTP PUT/PATCH call. Review the snippet below.
--- traveller-form.component.html ---
<div>
<!-- form fields below -->
<input type="text" [(ngModel)]="id" placeholder="Id" />
<input type="text" [(ngModel)]="firstName" placeholder="First Name" />
<input type="text" [(ngModel)]="lastName" placeholder="Last Name" />
<input type="text" [(ngModel)]="age" placeholder="age" />
<input type="text" [(ngModel)]="city" placeholder="City" />
<input type="text" [(ngModel)]="country" placeholder="Country" />
<!-- A button that invokes update action -->
<button (click)="updateTraveller()">Update Traveller</button>
</div>
--- traveller-form.component.ts ---
updateTraveller(){
let traveller:Traveller = {
id: this.id,
firstName: this.firstName,
lastName: this.lastName,
age: this.age,
city: this.city,
country: this.country
};
this.dataAccess.updateTraveller(traveller, this.id)
.subscribe(
success => alert("Done"),
error => alert(error)
);
}
Delete Traveller Record by using HTTP DELETE
For deleting a record, in the data-access service, use the delete method on Http Client object. It invokes the HTTP DELETE call. Unlike POST/PUT/PATCH, DELETE doesn’t have request payload. However, the record to be deleted is identified based on the id, provided in the URL. The server-side service identifies the record/entity to be deleted by Id. Consider the code snippet below.
--- data-access.service.ts ---
deleteTraveller(id: number):Observable<any>{
return this.client.delete<Traveller>(`${DATA_ACCESS_PREFIX}/${id}`)
.pipe(
catchError( (error: HttpErrorResponse) => {
return new ErrorObservable(`Error deleting travellers data. ${error.statusText || 'Unknown'} `);
}));
}
In the sample, the Traveller List screen has a delete button, which invokes the data access service’s deleteTraveller method. It provides the Id of the item to be deleted. On invoking the subscribe on the observable, the Http DELETE is called. Look at the code snippet below for the component’s code invoking delete.
--- traveller-list.component.html ---
<div *ngFor="let traveller of travellers">
<div> {{traveller.lastName}}, {{traveller.firstName}} is {{traveller.age}} years old.</div>
<div> City: {{traveller.city}}. Country: {{traveller.country}}</div>
<div><button (click)="deleteTraveller(traveller.id)">Delete</button></div>
<br />
</div>
--- traveller-list.component.ts ---
deleteTraveller(id){
console.log("deleting "+ id);
this.dataAccess.deleteTraveller(id).subscribe(
(msg) => console.log(msg),
(error) => console.log(error)
);
}
Setting up a server-side service for the sample
There are multiple ways to set-up server-side service for a disconnected UI application. It could be a sample, like the one we have been referring to so far. Or it could be mocked data for a real-world application.
Some applications go through a separate integration effort to access the real service. Meanwhile for Angular UI application development, they may use a sample JSON structure agreed upon with a service.
I’ll not go into pros and cons of this approach. However the idea is to get up and running with an Angular application and understand HTTP Client techniques, without having to worry much about server-side implementation (as for now).
The following approaches are suggested primarily only for a developer machine setup. These are not to be used in Production.
Option 1: JSON-Server
It is a pretty useful package for creating and exposing JSON objects as restful API. For the sample, we have data in a file named travellers.json under mock-data. We run the following command to have the travellers.json exposed as a RESTful API.
json-server ./mock-data/travellers.json
Prior to running the above command , install json server globally on the machine by using the following command.
npm install -g json-server
By default, JSON server runs on port 3000 and Webpack development server runs on port 4200. That means we run the application as http://localhost:4200 and access the API as http://localhost:3000. This leads to CORS problem for the developers.
CORS (Cross Origin Requests)
Browsers allow JavaScript to invoke API (XHR and fetch calls) on the same domain as that of JavaScript. If an Angular application is downloaded from domain xyz.com and if it can invoke API on abc.com, it can potentially lead to security problems.
On the developer machine, we can solve this problem by using a Webpack proxy configuration. The browser invokes API on the port 4200. The request and response for which in-turn is redirected to a different URL specified in the configuration (in this case port 3000). This abstracts browser from noticing a cross origin request.
Consider the following proxy configuration. It is saved on disk as proxy.conf.json
--- proxy.conf.json ---
{
"/travellers/*": {
"target": "http://localhost:3000",
"secure": false,
"logLevel": "debug",
"changeOrigin": true
}
}
To start the Angular application for debugging, use the following command. It ensures webpack picks the proxy configuration.
ng serve --proxy-config proxy.conf.json
Option 2: Angular In Memory Web API
In Memory Web API is another package that intercepts requests and mocks response using a mock JSON. One caveat is that it doesn’t make a network call. It’s in-memory to the browser.
Please note, Angular In Memory Web API is work in progress at the time of writing this tutorial. It may not be stable to upgrade or use it regularly, yet. But it is a useful idea, so continue to monitor its progress.
To begin, install Angular-In-Memory-Web-API
npm install --save angular-in-memory-web-api@0.5.4
Note: Considering this library is pre-release 0.5.4, it worked fine with the API attempted in the samples. Please review package ReadMe for newer versions and more information on the library.
To mock API response, use a mock service. Consider creating the following mock service.
--- mock-traveller-data-provider.ts ---
import { Injectable } from '@angular/core';
import { InMemoryDbService } from 'angular-in-memory-web-api';
@Injectable()
export class MockTravellerData implements InMemoryDbService {
constructor(){
}
createDb(){
let travellers = [
{
id: 2,
"firstName": "John",
"lastName": "Kelly",
...
},
{
id: 11,
"firstName": "Rahul Shrath",
"lastName": "Dravid",
...
}
];
return {travellers};
}
}
As we named the object being returned as travellers, it exposes API with the URL /api/travellers.
Notice createDb method overriding the function in InMemoryDbService. At the minimum, this method is required to be implemented in the class. This method returns the JSON objects that acts as an API response. It defines the mocked response structure.
Next, import the In Memory Web Api module and the mock service in Angular module.
--- app.module.ts ---
import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
import { MockTravellerData } from './services/mock-traveller-data-provider';
@NgModule({
declarations: [
..
],
imports: [
..
InMemoryWebApiModule.forRoot(MockTravellerData)
],
providers: [
...
],
bootstrap: [AppComponent]
})
export class AppModule { }
After the above changes to the code, HttpClient will no more make network calls. The Responses are mocked.
Conclusion
Server-side API interactions are integral for any UI application. Angular makes it easy to integrate with ready-made services in its common module. Content discussed so far, acts as a getting started guide for API integration.
This article was technically reviewed by Ravi Kiran.
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
V Keerti Kotaru is an author and a blogger. He has authored two books on Angular and Material Design. He was a Microsoft MVP (2016-2019) and a frequent contributor to the developer community. Subscribe to V Keerti Kotaru's thoughts at his
Twitter account. Checkout his past blogs, books and contributions at
kvkirthy.github.io/showcase.