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

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...