What is the best way to take any constructor type and transform it into a function type that can take the same arguments?

In the code snippet below, a class is created with a constructor that takes an argument of a generic type. This argument determines the type of the parameter received by the second argument. In this case, the first parameter sets the callback function's parameter type to MouseEvent:

class MyClass<T extends keyof HTMLElementEventMap>{
    constructor(tagName: T, listener: (ev: HTMLElementEventMap[T]) => any) { }
}

new MyClass("click", ev => { })
//                   ^^ (parameter) ev: MouseEvent

However, creating a function with rest arguments of type ConstructorParameters<typeof MyClass> results in resolving the callback parameter type as

Event | UIEvent | AnimationEvent | MouseEvent | FocusEvent | DragEvent | ErrorEvent | PointerEvent | ... 6 more ... | ClipboardEvent
.

function myFunction(...args: ConstructorParameters<typeof MyClass>) {}

myFunction("click", ev => { })
//                  ^^ (parameter) ev: Event | UIEvent | AnimationEvent | MouseEvent | FocusEvent | DragEvent | ErrorEvent | PointerEvent | ... 6 more ... | ClipboardEvent

How can I ensure the correct type for the callback parameter without having to redefine types within the myFunction function?

Any suggestions on achieving this?

Answer №1

What you require is to specify "take any constructor type and convert it to a function type that accepts the same arguments".


An issue arises because TypeScript's type system lacks a proper way to define "the arguments of something callable or constructible" that works for generic functions. For instance, if you have a function like:

function foo<T>(x: T, y: T): void { }

and attempt to extract its parameter list, the generic parameter T will be replaced with its constraint. In this case, T is unfettered, and thus has an implicit constraint of unknown:

type FooParams = Parameters<typeof foo>;
// type FooParams = [x: unknown, y: unknown]

TypeScript does not possess appropriate generic types to represent the parameter list of a generic function. A generic function has its generic type parameters on the call signature. However, a tuple type like [x: unknown, y: unknown] lacks a call signature and cannot hold a generic type parameter:

// Invalid in TS, do not utilize this:
type FooParams = <T>[x: T, y: T];

To address this, TypeScript would need something akin to arbitrary generic value types, as requested in microsoft/TypeScript#17574... but such a feature is absent.


Instead of focusing on a tuple type, perhaps we could automatically convert one generic function type into another. Unfortunately, once more, the language lacks suitable type operators for this task. To capture the relationship between a generic function and its type parameter, TypeScript would likely need something like "higher kinded types", as requested in microsoft/TypeScript#1213... but these are also nonexistent.


Prior to TypeScript 3.4, there may have been no solution. Nevertheless, Typescript 3.4 introduced support for higher order type inference from generic functions. While not a full implementation of higher kinded types, it enables transforming an actual generic function value into another generic function value, where the output function's type links to the input function's type precisely as needed. Although there is no available type-level syntax for using this feature, one can derive the desired type by inferring from a pseudo-function at the value level. An example for a class MyClass is demonstrated below:

// taking advantage of TS3.4 support for higher order inference from generic functions
const ctorArgs = <A extends any[], R>(f: new (...a: A) => R): (...a: A) => void => null!
const myFunc = ctorArgs(MyClass)
type MyFunctionType = typeof myFunc;

const myFunction: MyFunctionType = (...args) => {}
myFunction("click", ev => { ev.buttons })

Although ctorArgs accomplishes the intended type manipulation at the value level, it involves including unnecessary JavaScript code in the process. If the goal is solely focused on typing, this approach introduces unneeded complexity.

Considering the unavoidable inclusion of JavaScript code, one might leverage it to implement myFunction generically. The end goal being to take a constructor and transform it into a void-returning function - potentially discarding its result. This concept is exemplified here:

const makeMyFunction = <A extends any[], R>(
  f: new (...a: A) => R): (...a: A) => void =>
    (...a) => { new f(...a) } // whatever the implementation should do

const myFunction = makeMyFunction(MyClass);

In this scenario, myFunction is obtained effortlessly. Nonetheless, its suitability hinges on the specific use case which remains undisclosed.


Link to Playground for Code

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

Nest is unable to resolve the dependencies of the ItemsService. Ensure that the required argument at index [0] is present within the AppModule context

After following the Nest JS Crash tutorial from a Youtube Link, I encountered an error when importing an interface in the service. Nest seems unable to resolve dependencies of the ItemsService. It's important to ensure that the argument at index [0 ...

Enhance your Next.js routing by appending to a slug/url using the <Link> component

In my Next.js project, I have organized my files in a folder-based structure like this: /dashboard/[appid]/users/[userid]/user_info.tsx When using href={"user_info"} with the built-in Next.js component on a user page, I expect the URL to dynamic ...

Postpone the initial click action triggered by the specified directive

Is it possible to create a directive that prompts for confirmation when a button is clicked? This would involve storing the original event and only executing it once the user confirms their choice. A similar behavior has been mocked here: https://stackbl ...

The proper way to cancel useEffect's Async in TypeScript

I'm facing an issue with this straightforward example: useEffect(() => { axios.get(...).then(...).catch(...) }, [props.foo]) warning: can't perform a react state update on an unmounted component After some investigation, I found this ...

What is the recommended TypeScript type for setting React children?

My current layout is as follows: export default function Layout(children:any) { return ( <div className={`${styles.FixedBody} bg-gray-200`}> <main className={styles.FixedMain}> <LoginButton /> { children } ...

Broaden your interfaces by implementing multiple interfaces with Zod

Utilizing typescript, I am able to incorporate multiple interfaces interface Name { name: string } interface Age { age: number } interface People extends Name, Age { height: number } Is there a similar way to achieve this with Zod? What I attempted ...

When trying to access a certain class property, I was met with the following error message: TypeError: Unable to read/set property 'x' of

Lately, I've delved into the realm of JavaScript / TypeScript and decided to create a basic React App using TypeScript. Within one of my components, I aim to switch between different components using a "state" (where each component will follow the pre ...

Encountered a TypeError in Angular printjs: Object(...) function not recognized

I'm currently working on integrating the printJS library into an Angular project to print an image in PNG format. To begin, I added the following import statement: import { printJS } from "print-js/dist/print.min.js"; Next, I implemented the pri ...

Locate a specific item by its ID within a JSON file utilizing Angular version 2 or later

My JSON file structure is like the example below: { "id": "1", "country": "Brazil", "state": [ {"id": "1", "name": "Acre", "city": [ { "id": "1", "name": "Rio Branco"}, { "id": "2", "name": "Xapuri"} ...

Is there a way to track the loading time of a page using the nextjs router?

As I navigate through a next.js page, I often notice a noticeable delay between triggering a router.push and the subsequent loading of the next page. How can I accurately measure this delay? The process of router push involves actual work before transitio ...

Is there a way to verify in Angular whether an image link has a width and height exceeding 1000?

I'm currently working on a function that checks if an image linked in an input field has a width and height greater than 1000 pixels, and is in JPG format. Here's my approach: HTML: <input (change)="checkValidImage(1, product.main_photo)" [ ...

Tips for transforming a JSON Array of Objects into an Observable Array within an Angular framework

I'm working with Angular and calling a REST API that returns data in JSON Array of Objects like the example shown in this image: https://i.stack.imgur.com/Rz19k.png However, I'm having trouble converting it to my model class array. Can you provi ...

A guide on combining two native Record types in TypeScript

Is it possible to combine two predefined Record types in TypeScript? Consider the two Records below: var dictionary1 : Record<string, string []> ={ 'fruits' : ['apple','banana', 'cherry'], 'vegeta ...

Create a new project using Firebase Functions along with a Node.js backend and a React.js frontend

In the process of developing my application, I have chosen to utilize node.js, express.js, and Firebase with firebase functions, all coded in TypeScript. For the client side framework, I am interested in incorporating react.js. Currently, I have set up nod ...

Unable to globally install @angular/cli using Node.js on Red Hat software collection

After installing node.js 8 from Red Hat Software Collection (rh-nodejs8), I encountered an issue where I couldn't globally install TypeScript or @Angular/CLI because my bash session was restricted by scl-utils, requiring admin rights for global instal ...

Tsyringe - Utilizing Dependency Injection with Multiple Constructors

Hey there, how's everyone doing today? I'm venturing into something new and different, stepping slightly away from the usual concept but aiming to accomplish my goal in a more refined manner. Currently, I am utilizing a repository pattern and l ...

NextJS VSCode Typescript results in breakpoints becoming unbound

I have been following the instructions provided by Next.js from their official documentation on debugging using Visual Studio Code found here: https://nextjs.org/docs/advanced-features/debugging#using-the-debugger-in-visual-studio-code When attempting to ...

Angular 6: Exploring the Challenges of Extending Services Without Sacrificing the Functionality of ChildService

As I was developing multiple angular REST-services for my frontend, I came up with the idea of creating a base class BaseRestService to handle common functionalities like headers and helper functions. However, I encountered TypeErrors when trying to call ...

When choosing the child option, it starts acting abnormally if the parent option is already selected in Angular

I am encountering an issue while trying to select the parent and its children in the select option. The concept is to have one select option for the parent and another for the child. I have parent objects and nested objects as children, which are subCatego ...

When a class decorator is returned as a higher-order function, it is unable to access static values

Check out this showcase: function Decorator(SampleClass: Sample) { console.log('Inside the decorator function'); return function (args) { console.log('Inside the high order function of the decorator: ', args); let sample = ...