Tips for maintaining type information when using generics in constructors

class Registry<Inst, Ctor extends new (...args: unknown[]) => Inst, T extends Readonly<Record<string, Ctor>>> {
  constructor(public records: T) { }
  getCtor<K extends keyof T>(key: K) {
    return this.records[key]
  }
  getInst<K extends keyof T>(key: K) {
    const ctor = this.records[key]
    return new ctor()
  }
}

class Foo { foo: "foo" }
class Bar { bar: "bar" }

const registry = new Registry({
  foo: Foo,
  bar: Bar
})

const ctor = registry.getCtor('foo') // the type of ctor is "typeof Foo"
const inst = registry.getInst('foo') // the type of inst is "unknown", why not "Foo"?

The last two lines raise a question about why the constructor type was preserved while the instance type wasn't in the code.


Edit 1: Simplified version of the initial code

Answer №1

One issue to address is the excessive number of generic type parameters in your code. The constraint

T extends Readonly<Record<string, Ctor>>
does not provide an inference site for Ctor, and similarly, the constraint
Ctor extends new (...args: unknown[]) => Inst
fails to serve as an inference site for Inst. As a result, inference for Ctor and Inst will fall back on the constraints provided. Your code can be simplified to:

class Registry<T extends Readonly<Record<string, new (...args: unknown[]) => unknown>>> {}

To improve this, consider modifying it like so:

class Registry<T extends { [K in keyof T]: new () => object }> { }

This adjustment eliminates unnecessary complexity such as the use of Readonly or allowing arbitrary constructor arguments.


The main challenge arises when trying to interpret new ctor() within the context of the generic type T[K]. The compiler struggles to understand how to handle this generically without explicit guidance. One way to address this is by utilizing type assertions:

class Registry<T extends { [K in keyof T]: new () => object }> {
    constructor(public records: T) { }
    getCtor<K extends keyof T>(key: K) {
        return this.records[key]
    }
    getInst<K extends keyof T>(key: K) {
        const ctor = this.records[key]
        return new ctor() as InstanceType<T[K]> // assert
    }
}

While effective, this method requires manual verification of types rather than relying solely on the compiler.


A more efficient approach involves making the class generic based on the object of instance types rather than the type of records. By doing so, you simplify the structure and enhance readability using mapped types:

class Registry<T extends { [K in keyof T]: object }> {
    constructor(public records: { [K in keyof T]: new () => T[K] }) { }
    getCtor<K extends keyof T>(key: K) {
        return this.records[key]
    }
    getInst<K extends keyof T>(key: K) {
        const ctor = this.records[key]
        return new ctor();
    }
}

This modification allows for a clearer distinction between functions like getCtor() and getInst(), providing a smoother workflow within your codebase.

Code Playground Link

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

Experiencing difficulties while trying to showcase a PDF within the Expo Go app

I am currently developing a React Native application that requires the display of PDF files. I have tried two different methods, one using react-native-webview and the other with react-native-pdf, but both approaches are presenting challenges. This is how ...

Angular - Collaborative HTML format function

In my project, I have a function that sets the CSS class of an element dynamically. This function is used in different components where dynamic CSS needs to be applied. However, every time I make a change to the function, I have to update it in each compo ...

Building a filter for a union type in TypeScript: a step-by-step guide

Allow me to present an example to demonstrate my current objective. const v1: { type: "S"; payload: string } = { type: "S", payload: "test" }; const v2: { type: "N"; payload: number } = { type: "N", payload: 123 }; type Actions = typeof v1 | typeof v2; ...

Parsing JSON results in the return of two objects

I am analyzing a JSON file, expecting it to return Message[] using a promise. This code is similar to the one utilized in the Heroes sample project found in HTTP documentation { "data": [ {"id":"1","commid":"0","subject":"test1subject","body":" ...

The function for utilizing useState with a callback is throwing an error stating "Type does not have

Currently, I am implementing the use of useState with a callback function: interface Props { label: string; key: string; } const [state, setState] = useState<Props[]>([]); setState((prev: Props[]) => [...pr ...

Encountering a TypeError while attempting to retrieve an instance of AsyncLocalStorage

In order to access the instance of AsyncLocalStorage globally across different modules in my Express application, I have implemented a Singleton class to hold the instance of ALS. However, I am wondering if there might be a more efficient way to achieve th ...

404 Error: Unable to Locate Socket Io

I'm currently working on implementing a chat feature in Angular 2 using Socket IO, following this tutorial. However, I encountered an error message during a test on the server: GET http://localhost:3000/socket.io/?EIO=3&transport=polling& ...

Adjust puppeteer window dimensions when running in non-headless mode (not viewport)

Is there a way to adjust the browser window size to match the viewport size in Chrome(ium)? When only setting the viewport, the browser can look awkward if it is not running headfully and I want to visually monitor what's happening within the browser ...

Utilizing a package from your local computer

Due to my current circumstances, I am unable to utilize the npm publish command to release a package on the internet and subsequently use npm install. I also have no intention of publishing it on a remote server like nexus. Is there a method available to ...

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 ...

typescript ways to exclude enum values

I am working with enums in TypeScript. enum Status { Cancelled = 'cancelled', Completed = 'completed', Created = 'created' } Now, I need to create another enum that includes only the values Completed and Created. enum S ...

Can a universal type be designed for application across various types?

I've got this function: function stackPlayer(stack){ } The stack parameter can have one of the following forms only: a function that takes req, res, and next as arguments. a function that takes req, res, and next as arguments, and returns a functio ...

What are the best techniques for concentrating on a kendo maskedtextbox?

What is the correct way to set focus on the kendo-maskedtextbox in TypeScript after the view has initialized? The information provided in Telerik's example here is lacking in detail. ...

Why does WebStorm fail to recognize bigint type when using TSC 3.4.x?

Currently, I am working on the models section of my application and considering switching from using number to bigint for id types. However, despite knowing that this is supported from TSC 3.2.x, WebStorm is indicating an error with Unresolved type bigint. ...

Combining array elements into functions with RxJS observables

I am facing a scenario where I have an array of values that need to be processed sequentially using observables in RxJS. Is there a more optimized way to achieve this instead of using nested subscriptions? let num = 0; let myObs = new Observable(obs ...

Testing Angular components using mock HTML Document functionality is an important step in

Looking for help on testing a method in my component.ts. Here's the method: print(i) { (document.getElementById("iframe0) as any).contentWindow.print(); } I'm unsure how to mock an HTML document with an iframe in order to test this meth ...

The element 'fontFamily' is not recognized within the 'ThemeOptions' type in MUI theming

I'm diving into the world of React and MUI by building my own dashboard from scratch. Let's take a look at my App.tsx file: import React from 'react'; import ReactDOM from 'react-dom/client'; import './index.css'; i ...

Tips on transferring information to the graphical user interface

Here is my code snippet: signup.post('/signup', urlendcodedParser, async(req: Request, res: Response) => { const username = req.body.username; const password = req.body.password; const age = req.body.age; const email = req ...

Verify enum values within controller function

I am dealing with a query parameter in my REST API that should be restricted to specific values according to an enum type. I need to find a way to handle a "Bad Request" error if the client provides any value outside of this enum. Here is what my enum loo ...

What is the best way to reference class variables and methods within a callback function in Typescript?

While working on my Angular project with the Highcharts API, I encountered a situation where I needed to pass a state code to a class level method after drilling down to a specific map location. Below is the snippet of my current code: ngOnInit() { this. ...