What is the best way to create an abstract typescript generic type?

When working with the specified types below

interface Base {
  id: string;
}

interface A extends Base {
  propA: string;
}

interface B extends Base {
 propB: string;
}

I am looking to create a unique MyGeneric<T> with specific constraints:

  1. T must be an object
  2. T keys should be of type string
  3. T values must be of type instanceOf Base (either directly of type Base, or extending Base)

    (The third requirement has been rephrased for clarity and better understanding)

In my initial attempt, I created

interface MyConstraint {
  [key: string]: Base
}

interface MyGeneric<T extends MyConstraint> {
  data: T
}

However, this approach presents two drawbacks when used by the user:

interface userDefinedInterface1 {
  a: A;
  b: B;
}

function foo1(p: MyGeneric<userDefinedInterface1>):void {
  //drawback 1: encountering TS2344 error:
  //Type 'userDefinedInterface1' does not satisfy the constraint 'userDefinedInterface1'.   //Index signature is missing in type 'userDefinedInterface1'.
}

//To address drawback 1, the user must extend MyConstraint
interface userDefinedInterface2 extends MyConstraint {
  a: A;
  b: B;
}

function foo2(p: MyGeneric<userDefinedInterface2>):void {
  //drawback 2: all string properties are considered valid due to [key: string]
  console.log(p.data.arbitraryKey);//this is considered valid
}

Can we define interface MyGeneric<T> in a way that meets the three aforementioned constraints without these two issues?

Answer №1

Here is a solution that addresses both of your concerns:

type NewConstraint<M> = {
  [S in keyof M]: M[S] extends Original ? M[S] : never;
};

interface NewGenericType<M extends NewConstraint<M>> {
  info: M;
}

By making the NewConstraint generic, you can now use it in both of your scenarios. If you were to try:

interface CustomInterface2 {
  x: X;
  y: Y;
  z: string;
}

type NotCorrect = NewGenericType<CustomInterface2>;

You would receive an error indicating that the types of property z are not compatible.

Answer №2

There is just one condition - you must specify extra keys if you wish to add them.

interface Base {
  id: string;
  num: number;
}

interface A extends Base {
  propA: string;
}

interface B extends Base {
 propB: string;
}

type MyGeneric<T extends Base, K extends keyof any = never> = {
  data: T & {
    [key in K | keyof T]: key extends keyof T ? T[key] : string;
  }
}


const test1: MyGeneric<B, 'test'> = {
  data: {
    id: '123',
    num: 123,
    propB: '123',
    test: '123',
  },
};
const test2: MyGeneric<B> = {
  data: {
    id: '123',
    num: 123,
    propB: '123',
    test: '123', // fails, it has to be provided in current TS.
  },
};

Playground

If you want to rely solely on the keys of T.

then use this version:

type MyGeneric<T extends Base> = {
  data: T & {
    [key in keyof T]: key extends keyof Base ? Base[key] : string;
  }
}

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

Tips on avoiding updates to a defined object when a new object is filtered (created from the original object)

Is there a way to filter an array of objects based on their year without altering the original object? Whenever I apply a filter, it affects both the newly created object and the original one. However, I need the original object to remain unchanged so that ...

The type 'HTMLDivElement | null' cannot be assigned to the type 'HTMLDivElement' in this context

Struggling with a scroll function to maintain position while scrolling up or down - encountering an error: Error: Type 'HTMLDivElement | null' is not assignable to type 'HTMLDivElement'. Type 'null' is not assignable to type & ...

New Requirement for Angular Service: Subclass Constructor Must Be Provided or Unable to Resolve all Parameters for ClassName (?)

During a recent project, I encountered an issue while working on several services that all extend a base Service class. The base class requires a constructor parameter of HttpClient. When setting up the subclass with autocomplete, I noticed that their con ...

Dynamic Angular select options with ngFor causing cascading changes in subsequent selects

In my Angular 5 project, I am attempting to create a set of three <select> elements using *ngFor. I came across a helpful thread on Stack Overflow that provided some guidance (check it out here). However, I've encountered an issue where the sele ...

Refreshing Form in Angular 2

When I remove a form control from my form, it causes the form to always be invalid. However, if I delete a character from another input field and then add the same character back in (to trigger a change event), the form becomes valid as expected. Is ther ...

Enhance Graphql Queries with TypeOrm using Dynamic Filters

I am currently working on building a graphQL query system using Apollo, typescript, and TypeOrm. At the moment, I only have one table called users in my database. I am creating a getUsers GraphQL query which retrieves all users from the table. With the hel ...

React's componentDidUpdate being triggered before prop change occurs

I am working with the CryptoHistoricGraph component in my app.js file. I have passed this.state.coinPrices as a prop for this element. import React from 'react'; import axios from 'axios'; import CryptoSelect from './components/cry ...

Keep going in the for await loop in NodeJS

My code snippet looks like this: for await (const ele of offCycles) { if ((await MlQueueModel.find({ uuid: ele.uuid })).length !== 0) { continue; } <do something> } I'm curious if it's possible to use a continue st ...

Tips for triggering useEffect just once when various dependencies are updated simultaneously

Currently, I have implemented a useEffect hook with two dependencies in the following way: useEffect(() => { ..... }, [apiData, currentMeasurements]); It's important to note that the apiData is fetched using useLazyQuery from apollo/client. Upon ...

Is it possible to set the filtered information to the Reducer's state?

In my current project, I am implementing Typescript and Reducers. However, I am facing an issue where I am unable to update the state after deleting an item from an array of objects. The error message I receive is: Type '{ newData: Contactos[]; cont ...

Having trouble retrieving form values in Typescript React, only receiving HTML tag as output

I am having an issue with retrieving the form value to my useRef hook as it returns the HTML tag of the form instead. To solve this, I attempted to specify the type HTMLFormElement inside the chevrons and set null as the initial value for my useRef hook. ...

Refine the search outcomes by specifying a group criteria

I have a scenario where I need to filter out service users from the search list if they are already part of a group in another table. There are two tables that come into play - 'group-user' which contains groupId and serviceUserId, and 'gro ...

Implementing context menus on the Material-UI DataGrid is a straightforward process that can enhance the user experience

I am looking to enhance my context menus to be more like what is demonstrated here: Currently, I have only been able to achieve something similar to this example: https://codesandbox.io/s/xenodochial-snow-pz1fr?file=/src/DataGridTest.tsx The contextmenu ...

What is the best way to transform a string into emojis using TypeScript or JavaScript?

Looking to convert emoji from string in typescript to display emoji in html. Here is a snippet of the Typescript file: export class Example { emoji:any; function(){ this.emoji = ":joy:" } } In an HTML file, I would like it to dis ...

What is the best approach to upgrade this React Native code from version 0.59.10 to version 0.72.5?

I am encountering an issue with the initialRouteName: 'Notifications' property, while working on my React Native code. Despite trying various solutions, I have not been successful in resolving it. As a newcomer to React Native, any assistance wou ...

How can we create external labels for a polar chart in ng2-charts and chart.js, with a set position outside the circular rings?

Currently, I am working on creating a polar chart using Angular along with chart.js version 2.8.0 and ng2-charts version 2.3.0. In my implementation, I have utilized the chartjs-plugin-datalabels to show labels within the polar chart rings. However, this p ...

When using Angular's .navigateByUrl() method, it successfully navigates to the desired page, however the html content is not

Whenever I try to navigate to the home page after logging in, I encounter an issue. I have a navbar <app-header></app-header> with two links - one for signing up and another for logging in. After successfully logging in, my goal is to navigate ...

Can a reducer be molded in ngrx without utilizing the createReducer function?

While analyzing an existing codebase, I came across a reducer function called reviewReducer that was created without using the syntax of the createReducer function. The reviewReducer function in the code snippet below behaves like a typical reducer - it t ...

The switchMap function in Angular does not trigger the async validator as expected

To ensure that the username entered by the user is unique, I am sending an HTTP request for every input event from the target element. It is important to debounce this operation so that only one HTTP request is made for X consecutive input events within a ...

Separate an array in TypeScript based on the sign of each number, and then replace the empty spaces with null objects

Hey, I'm facing a little issue, I have an Array of objects and my goal is to split them based on the sign of numbers. The objects should then be dynamically stored in different Arrays while retaining their index and getting padded with zeros at the b ...