Choose between Typescript type a and Typescript type b

I am currently working with two interfaces:

interface IComment extends IData {
  comment: string;
}
interface IHistory extends IData{
  differences: any[];
  timeStamp: number;
}

Both of these interfaces extend another interface:

interface IData {
  user: string;
  date: Moment | string;
  isHistory: boolean;
}

Now, the issue arises when dealing with an array containing elements of both IComment and IHistory.

const data: Array<IHistory | IComment> = [...someHistoryArray, ...someCommentArray]

When trying to map over this array and access the timeStamp property, errors occur due to TypeScript not recognizing the specific types.

data.map((entry: IHistory | IComment) => {
  if(entry.isHistory) {
    entry.timeStamp 
    // TS2339: Property 'timeStamp' does not exist on type 'IHistory | IComment'. Property 'differences' does not exist on type 'IComment'.
  } else {
    entry.comment
    // TS2339: Property 'comment' does not exist on type 'IHistory | IComment'. Property 'comment' does not exist on type 'IHistory'.
  }
})

I have found two potential solutions, but they are not entirely satisfactory to me...

  1. I could manually cast the entry at each instance:

    (entry as IHistory).timeStamp 
    
  2. Alternatively, I could create a new variable with the correct type:

    const historyEntry: IHistory = entry as IHistory;
    

Are there any other possible solutions that could address this issue effectively?

Answer №1

If you want to differentiate between historical and non-historical data in a union, you can utilize the isHistory property and define specific characteristics in each interface:

interface IComment extends IData {
    comment: string;
    isHistory: false;
}
interface IHistory extends IData {
    differences: any[];
    timeStamp: number;
    isHistory: true;
}
interface IData {
    user: string;
    date:  string;
    isHistory: boolean;
}

let data: Array<IComment | IHistory>=[]
data.map((entry: IHistory | IComment) => {
  if(entry.isHistory === true) {
    entry.timeStamp //access historical data

  } else {
    entry.comment //access non-historical data

  }
})

Answer №3

To tackle this problem, one approach could be to implement a User-Defined Type Guard function. This function assists the compiler in determining whether a parameter matches a specific type. The code snippet below addresses your particular concern - I have included comments to highlight the modifications made.

interface IComment extends IData {
    comment: string;
}

interface IHistory extends IData {
    differences: any[];
    timeStamp: number;
}

interface IData {
    user: string;
    date: Moment | string;
    isHistory: boolean;
}

const data: Array<IHistory | IComment> = [];

data.map((entry: IHistory | IComment) => {
    // Narrowing down the type to IHistory explicitly within the block
    if (isHistory(entry)) {
        // entry.timeStamp
    } else {
        // entry.comment
    }
});

// Implementation of User-Defined Type Guard
function isHistory(data: IData): data is IHistory {
    return data.isHistory;
}

For more information on Advanced Types and User-Defined Type Guards, refer to this resource.

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

The custom marker created with Leaflet using divIcon does not seem to be styled with the specified

I'm currently working on customizing the leaflet marker using a divIcon and custom HTML. My aim is to have my marker displayed similarly to this example: https://i.sstatic.net/a5RnY.png So far, I've managed to create a marker and a divIcon with ...

What is the best way to incorporate a .json configuration into the environment.ts file and access an API with Angular

I need to import a Json file from the assets folder containing URLs like the following: config.json: { "url1": "https://jsonplaceholder.typicode.com/posts", "url2" : "https://reqres.in/api/users", ...

Error: Unable to locate module: Could not find 'react-server-dom-webpack/client.edge'

I've been trying to incorporate server components into my nextJS project, but I keep encountering an issue when using "use server" in my component. Error message: `./node_modules/next/dist/build/webpack/loaders/next-flight-loader/action-client-wrappe ...

Tips for utilizing generated *.d.ts files

I have been utilizing a Visual Studio 2017 extension called TypeScript Definition Generator to automatically create TypeScript interfaces for my MVC-ViewModels. Despite trying various similar tools, they all seem to result in the same output (*.cs.d.ts-Fil ...

Guide on how to showcase the template by leveraging the roomList information with ngTemplateOutlet in Angular

TS roomList = [{ name: 'Room2' }] HTML <div class="Layout-body"> <ng-container *ngFor="let dt of roomList; index as i" [ngTemplateOutlet]="Room1" [ngTemplateOutletContext]="{ data: dt, i: i }&qu ...

Angular Error: Unable to access property 'users' on a null value

I am working on a component that takes in data through the @Input() decorator regarding a group. My objective is to generate a new array of objects during the initialization of the component based on the data from the group array. Below is the TypeScript c ...

The Intersection Observer API is caught in a never-ending cycle of rendering

I am experimenting with the intersection observer API in order to selectively display elements in a CSS grid as the user scrolls, but I seem to have run into a problem of encountering an endless rendering loop. Below is the code snippet I am working with. ...

Tips for maintaining the menu state following a refresh

Is there a way to save the menu state when pressing F5? I'm looking for a similar functionality as seen on the Binance website. For example, clicking on the Sell NFT's submenu and then refreshing the page with F5 should maintain the menu state on ...

Typescript Error:TS2345: The argument '{ theme: string; jsonFile: string; output: string; }; }' is not compatible with the parameter type 'Options'

Encountering an error mentioned in the title while using the code snippet below: import * as fs from 'fs' import { mkdirp } from 'mkdirp' import * as report from 'cucumber-html-reporter' const Cucumber = require('cucumber ...

Is it possible to modify a single value in a React useState holding an object while assigning a new value to the others?

In my current state, I have the following setup: const [clickColumn, setClickColumn] = useState({ name: 0, tasks: 0, partner: 0, riskFactor: 0, legalForm: 0, foundationYear: 0 }) Consider this scenario where I only want to update ...

Creating an array with different types of objects involves specifying the types within the square brackets when

Here is an illustration of a type structure: type TFiltersTypes = 'selectableTags' | 'dropdown'; type TSelectableTabsFilterItem = { id: string; label: string; isSelected: boolean; }; type TFilter = { type: TFiltersType ...

Conditionally show a button in an Angular application based on the truthiness of a boolean value

I'm currently developing a simple angular Single Page Application (SPA) for a Pizzeria. Within my application, I have an array that contains various types of Pizzas with string, number, and boolean data types. Using this array, I am dynamically gene ...

Exploring the capabilities of argon2-browser in a cutting-edge setup with vite

After spending several hours attempting to implement the argon2-browser library in a Vue app with Vite, I have been encountering a persistent error. Despite following the documentation closely, I keep receiving the following message: This require call is ...

Discover the steps to dynamically set global data in Vue during runtime

I am currently working on a Vue application that requires fetching data from JSP at runtime, which means I cannot use .env files. As a solution, I am attempting to set data in Vue that can be accessed throughout the entire application (components, mixins, ...

Actions should be pure objects. Employ specialized middleware for handling asynchronous actions in Redux

I've encountered a dispatch error while using redux with TypeScript. It would be really helpful if someone could assist me in correcting the setup I currently have: Store: import { configureStore, combineReducers, MiddlewareArray, } from &ap ...

Oops! The 'map' property cannot be found in the type 'Observable<User>'

In my online shopping project that combines Angular and Firebase, I implemented the AuthGuard to verify user login status before accessing various links including ./check-out. However, I encountered an issue with importing map for Observable.User. All comp ...

Best Practices for Retrieving and Passing Data within a Resolver | Angular 10/11

Currently, I am working on fetching data into a component before it loads to customize some settings. However, I find the concept of resolver a bit confusing in terms of what it returns and how to interpret it. I am struggling with getting the correct dat ...

What causes the "Error: method not allowed" message to appear when attempting to send a "DELETE" request from a Next Js component? (The POST method is

This is my first time on this platform, and I'm currently following a tutorial from Javascript Mastery to create a clone of a thread application. After watching the entire video and building the basic functionality based on it, I decided to enhance th ...

openapi-generator is generating subpar api documentation for TypeScript

Executing the command below to generate an auto-generated API using openapi-generator (v6.0.1 - stable): openapi-generator-cli generate -i app.json -g typescript -o src/main/api The json file is valid. Validation was done using openapi-generator-cli valid ...

Unable to translate text on the loading page

Encountering a peculiar issue with the translate service. Here's how I set it up: export class AppComponent implements OnInit { constructor( private translateService: TranslateService, angulartics2GoogleAnalytics: Angulartics2GoogleAnalytics ...