Is it possible to associate a generic TypeScript type with another type by utilizing its generic parameters?

I am faced with a situation where I have a family of APIs consisting of various related generic types (along with associated constraints). To illustrate this, I have crafted an example using familiar types as placeholders for better understanding.

As I work with these types, there is a frequent need to create a new type based on an existing one, utilizing the generic parameters of the referenced type. This process is reminiscent of how an interface can restrict its property types based on the provided generic types in its definition (as demonstrated in the code snippet below through a workaround).

type Properties = { [name: string]: string }

interface SomeGeneric<A,B extends Properties,C extends HTMLElement> {
    someConcreteMethod: () => A
    someOtherConcreateMethod: (b: B) => void
    aConcreteProperty: C

    //  Dummy properties used for type derivation purposes only
    relatedInterface?: RelatedInterface<A,B>
    anotherRelatedInterface?: AnotherRelatedInterface<A,C>
    thirdRelatedInterface?: ThirdRelatedInterface<A,B,C>
}

... 
(remaining content remains unchanged)
...

The current approach may seem cumbersome and flawed in multiple aspects, mainly because it misleads the compiler into assuming that these objects possess properties of certain types when they are actually being utilized for transforming between constrained generic types.

This process does not align entirely with the concept of "mapped types", or any documentation on utilizing type constraints in generics. However, I did come across a StackOverflow question addressing extraction of type parameters from a generic type using 'infer', which somewhat relates to my goal.

Prior related questions

There are existing discussions about making one generic type dependable on another through constraints in TypeScript, but none specifically tackle the scenario of interdependence among different generic types within a similar context.

An example showcasing usage of 'infer' in manipulating generic type parameters to achieve desired transformations was found, resembling my objective albeit requiring further steps towards converting those extracted types into my related type structures.

My understanding and utilization of 'infer' remain limited, and I am yet to explore its potential application in scenarios like mine. Nonetheless, insights from solutions focusing on generic constraint dependencies might offer valuable perspectives for my case.

Answer №1

Regrettably, at this moment in time, it is not possible to extract the generic parameters of a type/interface without prior knowledge of the type itself. As a result, there is no foolproof solution available for this issue.

One approach would be to define a generic type for each category of interface related to other interfaces. The infer keyword must be utilized to accomplish this task. In essence, you can consider 'infer' as a variable declaration.

Here's an example from the documentation:

type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;

To put it simply, 'infer' acts as a parameter placeholder that evaluates both sides of a condition:

Array<string> extends Array<infer R>
, where 'R' must be 'string' in order for the equation to hold true. Restrictions can also be placed on the inferred parameter:

infer R extends string, indicating that 'R' should be considered as a 'string' rather than an unknown parameter by the compiler.

The same concept applies when dealing with multiple parameters without altering the underlying logic. Consider the scenario where 'T' extends 'SomeGeneric':

T extends SomeGeneric<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >

This allows us to obtain all three parameters of 'T' with the specified constraints ('string', 'Properties', 'HTMLElement'), which can then be passed to relevant interfaces accordingly.

Accessor for 'SomeGeneric':

type GetSomeGenericRelatedInterfaces<T extends SomeGeneric<any, any, any>> =
  T extends SomeGeneric<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >
    ? {
        relatedInterface?: RelatedInterface<A, B>;
        anotherRelatedInterface?: AnotherRelatedInterface<A, C>;
        thirdRelatedInterface: ThirdRelatedInterface<A, B, C>;
      }
    : never;

Accessor for 'ThirdRelatedInterface':

type GetThirdRelatedInterfaces<T extends ThirdRelatedInterface<any, any, any>> =
  T extends ThirdRelatedInterface<
    infer A extends string,
    infer B extends Properties,
    infer C extends HTMLElement
  >
    ? {
        relatedInterface?: RelatedInterface<A, B>;
        anotherRelatedInterface?: AnotherRelatedInterface<A, C>;
      }
    : never;

General wrapper:

type GetRelatedInterfaces<T> = T extends SomeGeneric<any, any, any>
  ? GetSomeGenericRelatedInterfaces<T>
  : T extends ThirdRelatedInterface<any, any, any>
  ? GetThirdRelatedInterfaces<T>
  : never;

Implementation:

type ConcreteB = { name: string };

interface SomeConcreteType
  extends SomeGeneric<string, ConcreteB, HTMLDivElement> {}

interface ThirdConcreteRelatedInterfaceVerbosely
  extends ThirdRelatedInterface<string, ConcreteB, HTMLDivElement> {}


// type Case1 = {
//   relatedInterface?: RelatedInterface<string, ConcreteB>;
//   anotherRelatedInterface?: AnotherRelatedInterface<string, HTMLDivElement>;
//   thirdRelatedInterface: ThirdRelatedInterface<
//     string,
//     ConcreteB,
//     HTMLDivElement
//   >;
// };
type Case1 = GetRelatedInterfaces<SomeConcreteType>;

// type Case2 = {
//   relatedInterface?: RelatedInterface<string, ConcreteB>;
//   anotherRelatedInterface?: AnotherRelatedInterface<string, HTMLDivElement>;
// }
type Case2 = GetRelatedInterfaces<ThirdConcreteRelatedInterfaceVerbosely>;

Try it out!

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

Set the subscription's value to the class property without changing its original state

Lately, I have been using the following method to set the value of a subscription to a property in my classes: export class ExampleComponent implements OnInit { exampleId: string; constructor(public route: ActivatedRoute) { this.route.params.subs ...

Issue TS1259: The module "".../node_modules/@types/bn.js/index"" can only be imported as the default using the 'esModuleInterop' flag

Currently, I am utilizing Hiro Stack.js which I obtained from the following link: https://github.com/hirosystems/stacks.js/tree/master/packages/transaction. For additional information, please refer to . Even when attempting to compile a fully commented out ...

Deciding between bundling a Typescript package and using tsc: When is each approach the best choice

When it comes to publishing a Typescript NPM package (library, not client), I have two main options: 1. Leveraging Typescript's compiler First option is to use the Typescript compiler: tsc and configure a tsconfig.json file with an outDir setting: { ...

Challenges arise when working with Vue 3.2 using the <script setup> tag in conjunction with TypeScript type

Hey there! I've been working with the Vue 3.2 <script setup> tag along with TypeScript. In a simple scenario, I'm aiming to display a user ID in the template. Technically, my code is functioning correctly as it shows the user ID as expect ...

What is the best way to obtain a distinct collection from two arrays that eliminates the second appearance of an element based on a key's value, rather than the first as in the Lodash uniqueBy function?

Let's say I have two arrays... const arr1 = [ { id: 1: newBid: true } ]; const arr2 = [ { id: 1, newBid: false }, { id: 2, newBid: false } ]; My goal is to end up with an array that looks like this [ { id: 1, newBid: false }, { id: 2, newBid: fals ...

What is the result of using `X ? A : B` in typescript?

type TestAny = any extends 'a' ? 1 : 2 // => 1 | 2 why??? how to interpret? type TestUnknown = unknown extends 'a' ? 1 : 2 // => 2 type TestStringA = 'a' extends 'a' ? 1 : 2 // => 1 type SomeUnion = ' ...

Is it impossible to use type as a generic in TypeScript?

Struggling with TypeScript in React and encountered an issue. I decided to use a generic to build an abstracted class related to Axios. However, I ran into an ESLint error when using any as the type parameter for my generic. ESLint: Unexpected any. Specif ...

What is the process for turning off deep imports in Tslint or tsconfig?

Is there a way to prevent deep imports in tsconfig? I am looking to limit imports beyond the library path: import { * } from '@geo/map-lib'; Despite my attempts, imports like @geo/map-lib/src/... are still allowed. { "extends": &q ...

Looking to retrieve the AssetLoadedFunc properties in the LoadAssets function? Wondering if you should use TypeScript or JavaScript

When I invoke this.AssetLoadedFunc within the function LoadAssets(callback, user_data) LoadAssets(callback, user_data) { this.glg.LoadWidgetFromURL("assets/Js/scrollbar_h.g", null, this.AssetLoaded, { name: "scrollb ...

Exploring the Incorporation of String as a Component Identifier in React and TypeScript

My input component can render either a textarea component (from a library) or a regular input. Check out the code below: import React, { useEffect, useRef, useState } from 'react' import './AppInput.css' interface Props { placehold ...

Accessing information independent of Observable data in TypeScript

When attempting to send an HttpRequest in Typescript, I encountered an issue where the received data could not be stored outside of the subscribe function. Despite successfully saving the data within the subscribe block and being able to access it there, ...

Utilizing TypeScript interfaces to infer React child props

How can I infer the props of the first child element and enforce them in TypeScript? I've been struggling with generics and haven't been able to get the type inference to work. I want to securely pass component props from a wrapper to the first ...

The Angular Material Table is not showing any data on the screen

My challenge is to consolidate data from 4 different endpoints in order to generate a ListElement that will populate an angular material table. Despite receiving the correct data in my logs, the table remains empty. Interestingly, when I include a conditio ...

How can I store various data types in a single array in TypeScript?

I have a scenario I need help with. Let's say we have two interfaces, Cats and Dogs. How can I create an array that can store both Cats and Dogs? interface Cats { name: string; age: number; } interface Dog { owner: string; } const cat1: Cat ...

Transform the data prior to sending it back as an observable

I am fairly new to Angular 2 and the idea of Observables. However, what I am trying to accomplish should be quite simple for experienced experts :) Here's the situation: I have a component where I have subscribed to an observable coming from a servic ...

Optimizing File Transfers and Streaming Using Next.js and CDN Integration

As I work on developing a download system for large files on my website using Next.js and hosting the files on a CDN, I face the challenge of downloading multiple files from the CDN, creating a zip archive, and sending it to the client. Currently, I have i ...

The callback function `(err: any, data: any) => void` does not share any properties with the type `RequestInit`

Inspired by the tutorial at , I am working on a time-based visualization. I am currently using version "d3": "^5.4.0". Here is the code snippet: d3.json('http://127.0.0.1:5000', function (err, data) { if (err) throw err; // Cre ...

TypeScript equivalent to Python's method for removing non-whitespace characters is achieved by

I understand that I can utilize .trim() to eliminate trailing spaces Is there a method to trim non-space characters instead? In [1]: str = 'abc/def/ghi/' In [2]: s.strip('/') Out[2]: 'abc/def/ghi' I am referring to the funct ...

Implementing Bootstrap 5 JS within an Angular 11 component TypeScript

I am currently working on a project that utilizes Angular 11 and we are aiming to integrate Bootstrap 5 native JS without relying on third-party libraries like ng-bootstrap, MDB, or ngx-bootstrap (jQuery is not being used as well). I understand that using ...

Error encountered: The term 'interface' is a restricted keyword

I am in the process of developing a NodeJS and MongoDB library for performing CRUD operations on APIs. My goal is to establish an interface with Typescript that includes the url and database name, structured as follows: However, I am encountering this par ...