Angular single-page applications use massive Ajax requests to communicate with webservers and retrieve data from databases. In this article, I’ll give a couple of hints about how to manage data response. This will help you set up a good designed layer for your Ajax flows.

The Basics: Observables and HttpClient
From Angular 4 to Angular 8, the framework started to make a massive use of Observables and Promises to handle data fetching from AJAX. Some versions of Angular seemed to prefer Promises, some others Observables, but the goal is basically always the same. We have a Service method, invoked from our Angular Component, and we want to be notified when data from the server pops out.
Promises and Observables are quite different. Keep in mind that Promises are simple objects with fullfilled and rejected possible states, and Observables are “streams” over the requests. Observables are way more versatile if you need to have control of those requests. I won’t spend more time on Promises, but be sure to choose between Observables and Promises very carefully. We’ll focus the discussion on the Observables implementation. We’re focusing on Observables because Observables are presented as the default implementation in the current official Angular documentation for devs.
HTTP JSON Responses: Unstructured Version
When your goal is to fire an Ajax request from an Angular service, you start coding something like this:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root'
})
export class AjaxService {
endPoint = `/assets/candies.json`
constructor(
private http : HttpClient
) { }
getCandies(){
return this.http.get(`${this.endPoint}`)
}
}
The
getCandies()
method here is very plain and unstructured. We’re using type inference to return to the caller a generic Observable because it’s the return type of the get()
method in HttpClient. So, what’s the type handled by the Observable here? It’s just a typescript “any.”
This code works, but when the Component subscribes to the
getCandies()
method, what will it get from that? Definitely an error if the HTTP status code is 500 or any other error. And if it does succeed? Easy, you get an “any” object. It’s like saying we want to use the Object class in Java. When we access to that Object’s field, we’ll have no idea what’s inside it. Then, we could easily end up getting undefined values (such as a Java NullPointerException
).Observable Interfaces to Handle Responses: First Approach
So, our goal is to represent the structure of the fetched data using a Typescript Interface. Suppose we want to fetch Candies from a JSON:
[ { color : “yellow”, flavour : “lemonade”, calories : 24 }, { color : “brown”, flavour : “chocolate”, calories : 89 }, ...]
It’s easy to code something:
export interface Candies {
color: string,
flavour: string,
calories: number
}
And update the service method implementation like this:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Candy, AjaxResponse } from '../interfaces/ajax.interfaces';
@Injectable({
providedIn: 'root'
})
export class AjaxService {
endPoint = `/assets/candies.json`
constructor(
private http : HttpClient
) { }
getCandies() : Observable<AjaxResponse<Candy[]>>{
return this.http.get<AjaxResponse<Candy[]>>(`${this.endPoint}`)
}
}
So, now we got some benefits in our first code revision. Now we have an Interface to map fields and define the Candy structure. It’s fine because our Component will now receive a Candy, not a generic “any” Object. The Component will be happy to know what it gets and what to access:
import { Component } from '@angular/core';
import { AjaxService } from './services/ajax.service';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
title = 'AngularHttpDemo';
constructor(
private ajax : AjaxService
){}
ngOnInit(){
this.ajax.getCandies().subscribe(
(candies) => {
for(let c of candies){
console.log(`
COLOR : ${c.color} \n
FLAVOUR : ${c.flavour} \n
CALORIES : ${c.calories} \n
`)
}
},
(err) => { console.error(err) }
)
}
}
This is the most popular implementation I’ve seen over several Angular projects. It’s good because it’s lightning-fast to code, easy to understand, and most of the time we don’t need anymore.
Why This Might Not Be Enough: Final Approach
When your application starts growing, we start coding infinite kinds of Interfaces that share nothing between them. Typescript is not Java, but it provides good support to OOP operations and hierarchy. We could use inheritance and suppose that there is a generic implementation of responses and then subclass the generic class. It’s a good idea, but we can do better.
If you‘re good at class modeling, you know that we could approach something similar to Java Collections. These are structures that can handle and contain almost anything. The only requirement is that the data must be an Object subclass. To work, Java SDK uses Generics. A Generic is a “placeholder” to something that could be any kind of specific class. So if we define an ArrayList of Generics, we can easily select the class to handle in the ArrayList declaration.
In Angular HTTP Requests, Generics are the best choice to handle data that has a common structure over a JSON produced with the same model. This implementation really depends on your web services’ specifications, and it’s not mandatory. But if you have control over your back end (or you can discuss and define common response structure with back end developers), you can develop the front end app using Generics. Suppose that your back end wants to respond in any case, for all invocations, with a JSON like this:
{
success : true,
errorMessage : "Something went wrong", // optional
data : [...] // changes according to WS specs
}
It could be a good idea to establish a common HTTP response for all web services invocations. It will represent a sort of default AppResponse for your microservice, and it will be easy to read and write documentation to.
So, back to front end, how do we handle it in our HttpClient Observables? The problem here is the data — it has many forms and is related to different models. Use a Generic to map “data” type, a boolean, and a string for shared and common fields:
export interface AjaxResponse<T> {
success : boolean,
errorMessage? : string,
data : T
}
In this code, we’re defining an AjaxResponse interface that will reflect the Generic type to the data field. It’s very versatile. You can even think about more than one Generic implementation, so it’s perfect for all kinds of uses. Now, anytime we have to produce an Observable containing Candies, Cookies, or anything else, we just have to specify the Generic type:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Candy, AjaxResponse } from '../interfaces/ajax.interfaces';
@Injectable({
providedIn: 'root'
})
export class AjaxService {
endPoint = `/assets/candies.json`
constructor(
private http : HttpClient
) { }
getCandies() : Observable<AjaxResponse<Candy[]>>{
return this.http.get<AjaxResponse<Candy[]>>(`${this.endPoint}`)
}
}
Generics are a lot of fun and provide a very good way to “generalize” implementations of types. Remember that Generics are the only way to make a type change or adapt to specific circumstances — “any” types aren’t the same thing. An “any” parameter in a method can host any kind of parameter type, but it will make your compiler lose the type reference. This is because the object transforms into “any.”
It’s true that JSON requests, params, and responses have no type because JSON is an unmatched JavaScript type. But if you want to design a good application, you can’t avoid Interfaces if you don’t want to lose data control and management.
Try it yourself, and you will appreciate Typescript Generics implementations.
Comments
Post a Comment