The concept of HttpClient type safety appears to neglect the use of interfaces

TL;DR:

  1. A specific angular interface was linked to HttpClient.get() responses.
  2. The responses were transformed into a seemingly general object type.
  3. Even though attributes like id and title were not defined on the interface, they were still accessible in the response object as well as in the .html file (e.g.
    <p>Title is:
    {{message?.title}}</p>
    .
  4. If an interface acts as a contract, should it not restrict some of this behavior?

Transitioning from a Java background to Angular 4 where I encountered issues with type safety in HttpClient. According to this guide, one can use an interface when making a call to http.get<> to validate the HTTP response.

Let's take a simple interface with just a single field foo:

export interface FooInterface{
   foo: string;
}

Following the guidance mentioned above, we can easily connect this interface with a response:

export class RestComponent {

   message: FooInterface;

    http.get<FooInterface>('/api/items').subscribe(data => {
        this.message = data;
        console.log(this.message);
        console.log(typeof(this.message));
        console.log(this.message.foo);
    });

Here's the issue: when I hit an API that doesn't include a foo field. The real API provides two fields instead: id and title. Surprisingly, even though the expected field was missing, a data object was still created:

https://i.sstatic.net/xW9g9.png

This data object now contains fields id and title. Trying to access data.foo will return undefined.

Moreover, although the interface stops me from directly accessing these id and title fields, they can still be accessed using calls like data['id'] and referenced in the HTML file.

<h1>
  id: {{message.id}}
  <br>
  title: {{message?.title}}
</h1>

Surprisingly, this works! Essentially, the only thing the interface appears to prevent is something like:

`this.message.title`

That seems alright... but considering that these fields can still be accessed using this.message['title'] or in html files, what's really the purpose of the interface?

https://i.sstatic.net/Pw4cU.png

Answer №1

Transitioning from a Java background to TypeScript, one of the challenges is the lack of strict type enforcement. In TypeScript, when casting an object, there is no runtime exception thrown if the object does not match the specified class. Instead, any errors are only detected later when trying to access missing fields.

The goal is not just compile-time type checking (as TypeScript offers), but rather enforcing a specific response type. One way to achieve this is by manually validating the response:

http.get<FooInterface>('/api/items').subscribe(data => {
    if (typeof data !== 'object')
      throw new Error('Expected an object')
    if (typeof data.foo !== 'string')
      throw new Error('Expected a string property')
    this.message = data;
});

For automated validation, consider using JSON Schema. By defining a JSON schema alongside your TypeScript class, you can ensure consistency. There are tools like this library that can assist in keeping them synchronized. Additionally, there are libraries that can generate schemas automatically from sources like Spring REST endpoints, ensuring alignment with backend services.

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

ng-for loop not properly rendering array of collections in table cells

I've been working on developing a MEAN Stack application that deals with mongoDB data related to hostels. hostels { "_id" : ObjectId("5e3c21c03d8d54b35af796ed"), "Hostel" : "The traveler's Lodge", "Rooms" : 23, "Customer" : [ { ...

Unveiling the power of experimental decorators in Storybook 8 with NextJS/SWC

I am facing an issue with experimental class decorators in my code, causing the Storybook build to crash. Module build failed (from ./node_modules/@storybook/nextjs/dist/swc/next-swc-loader-patch.js): Error: × Expression expected Despite reading the co ...

How can we effectively utilize LESS variables in styles.less when working with LESS files within components in Angular versions 6 or 7?

Running Angular version 7.0.0 will generate a folder structure typical for "ng new". Below is the content of my styles.less file: @personal-black: #0000; This snippet shows the content of my app.component.less file: ...

A guide on implementing Angular ngbPopover within a CellRenderer for displaying in an ag-grid cell

I successfully set up an Angular Application and decided to utilize ag-grid community as a key component for displaying data from a backend API in tables, using fontawesome icons to enhance readability. While everything looks fine and my application is fu ...

Converting an integer into a String Enum in TypeScript can result in an undefined value being returned

Issue with Mapping Integer to Enum in TypeScript export enum MyEnum { Unknown = 'Unknown', SomeValue = 'SomeValue', SomeOtherValue = 'SomeOtherValue', } Recently, I encountered a problem with mapping integer val ...

The RxJS Map operator may struggle to detect changes in an array that has been

EDIT: My question has been flagged as a duplicate. Despite my efforts to search for a solution, I failed to consider the full context of the issue and wasted hours using incorrect keywords. I have acknowledged the helpful response provided once the mistake ...

Ways to adjust the ngx-pagination color scheme?

I am looking to customize the background color of ngx-pagination Here is my current code: <pagination-controls class="custom-pagination" id="indicadorPaginationResults" (pageChange)="p=$event" maxSize="9" directionLinks="true" autoHide="true" previ ...

Position the div beneath another div using the display:grid property

I can't seem to figure out how to position a div below another div using display:grid. The div is appearing on top instead of below. The "app-bm-payment-card-item" is actually a custom Angular component. Here's the HTML file: //div wrapper < ...

An error in Webpack prevents it from resolving the data:text import

I built an app that relies on a third-party library with the following syntax: const module = await import(`data:text/javascript;charset=utf-8,${content}`); While using Webpack to build the app, I encountered this error: ERROR in ./node_modules/@web/test- ...

Getting the PlayerId after a user subscribes in OneSignal with Ionic2

Currently working on an app with Ionic2 and facing a challenge with retrieving the player id after a user subscribes in order to store it in my database. Any suggestions on how I can retrieve the unique player id of OneSignal users post-subscription? ...

Encountering [Object Object] within an angular2 app

https://i.stack.imgur.com/iceKH.pngI recently created an angular2 application using ngrx/effects for handling http calls. My reference point was an application on GitHub. However, I am facing an issue where the response from the HTTP call is not displaying ...

Simultaneously leveraging angular and node

Currently, I'm developing a basic Angular application and so far everything on the Angular side is functioning properly. In order to incorporate Express into my project, I created a file called server.js. However, when attempting to run node server.j ...

Alter the background color of a table cell in Angular HTML depending on a boolean value

Attempting to use Angular ng-class to set the background color of a table cell. I'm referencing code snippets from these resources: Angular: How to change the color of cell table if condition is true Change HTML table cell background color using ...

The "main" entry for ts-node is not valid when running ts-node-dev

Recently, I embarked on a TypeScript project using yarn where I executed the following commands: yarn init -y yarn add typescript -D yarn tsc --init yarn add ts-node-dev -D Subsequently, I crafted a script titled dev that triggers tsnd src/index.ts, howev ...

How to Retrieve the Access Token from Instagram using Angular 2/4/5 API

I have integrated Instagram authentication into my Angular 2 project using the following steps: Begin by registering an Instagram Client and adding a sandbox user (as a prerequisite) Within signup.html, include the following code snippet: <button t ...

Navigating back in an Async Function within Angular

I am encountering an issue while trying to incorporate an if else condition within my async function in Angular. To prevent the error, I am required to include a return statement in my async function. https://i.sstatic.net/2foil2jM.png asyncFunction: (v ...

Challenges with formArrayName in Angular can be tricky to navigate

I am in the process of developing an Angular library with the main feature being the provision of a selector with two inputs: a reactive form containing the data an object literal specifying which properties of the data should have editable input fields ...

What is causing certain code to be unable to iterate over values in a map in TypeScript?

Exploring various TypeScript idioms showcased in the responses to this Stack Overflow post (Iterating over Typescript Map) on Codepen. Below is my code snippet. class KeyType { style: number; constructor(style) { this.style = style; }; } fu ...

Angular Material Textbox with drop shadow

Currently working on a form design and aiming for the input box to resemble the image provided within the angular material matInput framework. Any suggestions on how to accomplish this? Attached is a visual representation of the desired input box appearan ...

Navigate between Angular components using [routerLink] in Angular router

How can I navigate from localhost:4200/home to localhost:4200/home#app=1111? I attempted the following: home.component.html <a class="app-card" [routerLink]="['/HOME']" [queryParams]="{'app':'1111'}"> However, nothin ...