Skip to main content

Modeling Typescript Interfaces to Support Angular HTTP requests

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 {
    colorstring,
    flavourstring,

    caloriesnumber
}
And update the service method implementation like this:
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { CandyAjaxResponse } 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 { CandyAjaxResponse } 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

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...