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

Using Node.js and TypeScript to define custom data types has become a common practice among developers

I offer a variety of services, all yielding the same outcome: type result = { success: boolean data?: any } const serviceA = async (): Promise<result> => { ... } const serviceB = async (): Promise<result> => { ... } However, th ...

Expanding the properties of an object dynamically and 'directly' by utilizing `this` in JavaScript/TypeScript

Is it possible to directly add properties from an object "directly" to this of a class in JavaScript/TypeScript, bypassing the need to loop through the object properties and create them manually? I have attempted something like this but it doesn't se ...

What are some ways to create a dynamic child server component?

Take a look at the following code snippet // layout.tsx export default function Layout({children}: any) { return <div> {children} </div> } // page.tsx export const dynamic = "force-dynamic"; const DynamicChild = dynamic( ...

Utilizing the variables defined in the create function within the update function of Phaser 3

I'm facing an issue in my game where I can't access a variable that I declared in the create function when trying to use it in the update function. Here is a snippet of what I'm trying to achieve: create() { const map = this.make. ...

Transforming a JavaScript component based on classes into a TypeScript component by employing dynamic prop destructuring

My current setup involves a class based component that looks like this class ArInput extends React.Component { render() { const { shadowless, success, error } = this.props; const inputStyles = [ styles.input, !shadowless && s ...

Error in Typescript: The element is implicitly assigned an 'any' type due to the inability to use a 'string' type expression as an index

I'm a beginner with TypeScript and I encountered an error that's confusing to me while trying to follow an online tutorial on sorting data in Reactjs using React hooks. Here is the section of my component code where the error occurs: Element imp ...

`express-validator version 4 is not functioning as expected`

Trying to implement input validation using express-validator v4.3.0 for my express routes, but despite following the documentation, I am unable to get it working correctly. It seems to not detect any errors and also gets stuck in the route. Could it be tha ...

Utilizing References in React Components

One of the challenges I am facing involves a Container that needs references to some of its child components: const Container = () => { const blocks: HTMLDivElement[] = []; return ( <div> <Navigation currentBlock={currentBlock} ...

Having trouble getting Jest to manually mock in Nestjs?

When setting up a mock service like this: // /catalogue/__mock__/catalogue.service.ts export const CatalogueService = jest.fn().mockImplementation(() => { return { filterRulesFor: jest.fn().mockImplementation((role: Roles): Rule[] => rules.filt ...

Display the new data from an array that has been created following a subscription to Angular Firestore

I am struggling to access the content of a variable that holds an array from a Firebase subscription. The issue I am facing is that I am unable to retrieve or access the value I created within the subscription. It seems like I can only use the created valu ...

Receiving an error in Typescript when passing an object dynamically to a React component

Encountering a typescript error while attempting to pass dynamic values to a React component: Error message: Property 'title' does not exist on type 'string'.ts(2339) import { useTranslation } from "react-i18next"; import ...

Error: The term 'makeStyles' cannot be found in the '@material-ui/core' module after installing Material-ui-pickers

Can anyone help me with using the inlineDatePicker components from Material UI pickers? I found them here: After running the npm -i command, I encountered an error during compilation: Failed to compile. ./node_modules/material-ui-pickers/dist/material-u ...

Exploring the behavior of control flow in Typescript

I am a beginner when it comes to JS, TS, and Angular, and I have encountered an issue with an Angular component that I am working on: export class AdminProductsMenuComponent implements OnInit{ constructor(private productService: ProductService, ...

Issue with MongoDB $push within an Array of Arrays: The shorthand property 'elements' does not have a value available in scope

I am encountering an issue while trying to insert data into Elements in MongoDB using TypeScript. Any suggestions on how to resolve this problem? Attempt 1 Error: I am receiving an error message stating "No value exists in scope for the shorthand property ...

Can we securely retrieve nested properties from an object using an array of keys in TypeScript? Is there a method to accomplish this in a manner that is type-safe and easily combinable?

I wish to create a function that retrieves a value from an object using an array of property keys. Here's an example implementation: function getValue<O, K extends ObjKeys<O>>(obj: O, keys: K): ObjVal<O,K> { let out = obj; for (c ...

What is the process for importing components from a Stencil library into a React application?

After successfully creating: a stencilJS component library named "my-lib" using the "npm init stencil" wizard and an Ionic React app using "ionic start myApp tabs" I am now trying to incorporate the default "my-component," aka MyComponent from my-lib. H ...

Steer clear of using the non-null assertion operator while assigning object members

Looking for a practical method to assign object members to another object without using the non-null assertion operator "!". In the example below, consider that formData can be any JavaScript object. some.component.ts export class SomeComponent { someMo ...

Issue with locating assets in Angular 6 build

Hey there! I'm currently facing an issue while trying to build my angular project. In the project, I am using scss with assets, and below is a snippet of how I have defined the background image: .ciao { background-image: url("../../assets/images/bc ...

Retrieving a value in the app component from a map using Angular

I have been struggling to display the values of ValueM, ValueR, and product in my app.component.html file. Can anyone offer a solution or tip to help me move forward? Thank you very much. app.component.ts forkJoin( this.service1.method1(filter1), ...

Maximizing Performance: Enhancing Nested For Loops in Angular with Typescript

Is there a way to optimize the iteration and comparisons in my nested loop? I'm looking to improve my logic by utilizing map, reduce, and filter to reduce the number of lines of code and loops. How can I achieve this? fill() { this.rolesPermiAdd = ...