Nested self-referencing in Typescript involves a structure where

Please note that the code below has been simplified to highlight a specific issue. The explanation before the code may be lengthy, but it is necessary for clarity.

Imagine I have a Foo class that represents a complex object.

interface Config {
    bars:{
        [key:string]: {
            on?: {
                [key:string]: (m:any) => void
            }
        }
    }
}

class Foo<T extends Config> {

    public constructor(private config:T) {}

    public doSomething(eventName: keyof T["bars"]) {}
}

The configuration of this class is provided through an object passed in the constructor. For example:

const foo = new Foo({
    bars: {
        buz1: { },
        buz2: { }
    }
})

foo.doSomething("buz1");
foo.doSomething("foo");

The first call to doSomething works fine, while the second one raises an error as expected. Now, my challenge lies in the nested buz* objects which must have an on property specifying event names and associated callbacks when events occur:

const foo = new Foo({
    bars: {
        buz1: {
            on: {
                "event": (f:Foo<THereIsTheIssue>) => {
                    f.doSomething("buz2")
                }
            }
        },
        buz2: { }
    }
})

I want the variable f to be of the same type as foo, but I'm struggling to communicate that to TypeScript. The closest solution I've found so far is:

interface Config<U extends Config<U>> {
    bars:{
        [key:string]: {
            on?: {
                [key:string]: (m:Foo<U>) => void
            }
        }
    }
}

class Foo<T extends Config<T>> {

    public constructor(private config:T) {}

    public doSomething(eventName: keyof T["bars"]) {}
}    

function tmp() {
    const foo = new Foo({
        bars: {
            buz1: {
                on: {
                    "event": (f) => {
                        f.doSomething("")
                    }
                }
            },
            buz2: { }
        }
    });

    foo.doSomething("buz1");
    foo.doSomething("foo");
}

However, the issue is that f ends up being of type Foo<Config<unknown>>, making it incompatible with the assignment to event.

So, how can I make TypeScript recognize the type based on what is supplied to the constructor (if possible)?

Here are additional constraints to consider:

  • The types can either be separated or combined into a single type/interface (with many other properties)
  • bars and on are fixed keywords that need to be nested as shown
  • buz* are dynamic and will vary depending on the developer/project

You can find the Gist link and the code snippet on the TypeScript playground.

Answer №1

It seems that the task you are attempting to achieve may not be feasible due to the fact that it leads to a recursive or circular type declaration.

Consider this scenario: let's extract the object literal used in the instantiation of Foo into a separate variable:

const config = {
    bars: {
        buz1: {
            on: {
                event: (m: Foo<TypeWeAreLookingFor>) => {
                    m.doSomething("")
                }
            }
        },
        buz2: { }
    }
}
const foo = new Foo(config)

How can we replace TypeWeAreLookingFor? It would require referencing the current variable type (config), which is not supported in Typescript, or creating a new type for config. However, this approach also leads to the same issue as you would need to define the new ConfigType in terms of Foo<ConfigType>.

A closer look at the type schema reveals the problem: Foo<V> essentially equates to Foo<T>, resulting in describing Foo<T> in reference to itself, leading to a recursive reference.

In essence, addressing the challenge of generating instance methods from object properties programmatically presents a significant dilemma. While utilizing reflection patterns may cause runtime issues contrary to TypeScript principles, lack of predefined method information leaves limited options.

One potential solution could involve implementing a "method factory" function allowing definition of doSomething for each event, or alternatively, instantiating classes tailored to handle specific behaviors based on known events.

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

Typescript: Implementing a generic function with the flexibility of an optional parameter

Having some difficulty writing a generic function with an optional parameter type Action<TParameters = undefined> = (parameters: TParameters) => void const A: Action = () => console.log('Hi :)') // This works as expected const B: ...

What is the best way to access the vue3datepicker object in order to manually close the date picker popup user interface?

Enhancement After yoduh's feedback, I made adjustments to the code below. However, vue3datepicker is still undefined. Code has been updated according to yodubs suggestion. I consulted the official vue3datepicker documentation to customize my own Act ...

What is the best way to include bootstrap using webpack?

I am currently building a webapp using Typescript and webpack. I have been able to successfully import some modules by including them in my webpack.config.js file as shown below. However, no matter how many times I attempt it, I cannot seem to import the b ...

Establish a connection to Cosmos DB from local code by utilizing the DefaultAzureCredential method

I've created a Typescript script to retrieve items from a Cosmos DB container, utilizing the DefaultAzureCredential for authentication. However, I'm encountering a 403 error indicating insufficient permissions, which is puzzling since I am the ad ...

What is the reason for IE displaying null when the model does not exist?

Why does IE 11 render 'null' if my model does not exist? For instance: <tr> <td [innerHTML]="model?.prop1 | my-pipe"></td> </tr> Imagine this scenario: When the page loads, a request is sent to the server and the res ...

Issue with selecting a value in React MUI and default value not being defined

Currently, I am working on creating a form in React using MUI and Formik. While implementing the select feature with default values fetched from an API object, I encountered issues where the select function was not working as expected. Strangely, I couldn& ...

What are the steps to effectively implement the useEffect hook in React?

I'm facing an issue where I am trying to return a function that utilizes useEffect from a custom usehook, but I keep getting the error "useEffect is called in a function which is neither a react function component nor a custom hook." Here's what ...

Is there a way to utilize the 'interval' Rxjs function without triggering the Change Detection routine?

My goal is to display the live server time in my application. To achieve this, I created a component that utilizes the RXJS 'interval' function to update the time every second. However, this approach triggers the Change Detection routine every se ...

Warning: The attribute 'EyeDropper' is not recognized within the context of 'Window & typeof globalThis'

Attempting to utilize "window.EyeDropper" in a project that combines vue2 and TypeScript. When writing the following code: console.log(window.EyeDropper); An error message is generated by my Vetur plugin: Property 'EyeDropper' does not exist on ...

What is the best approach to implement server-side rendering in Next.js while utilizing Apollo React hooks for fetching data from the backend?

I have a Next.js project that is utilizing Apollo GraphQL to retrieve data from the backend. My goal is to render the page using server-side rendering. However, I am encountering an obstacle as the React hooks provided by GraphQL Apollo prevent me from cal ...

Utilizing enum values in the HTML value attribute with Angular 2

I'm attempting to utilize an enum value in order to set the selected value of an HTML attribute: export enum MyEnum { FirstValue, SecondValue } export function MyEnumAware(constructor: Function) { constructor.prototype.MyEnum = MyEnum; } ...

The error message states that the property 'registerUser' is not found on the class 'UserController'

In the controller file, I exported two functions (registerUser and loginUser) as default. No errors were thrown at that stage, but when attempting to access the routes, an error occurred stating - Property 'registerUser' does not exist on type &a ...

An issue has occurred: the function cannot be applied to the intermediate value that is currently being processed

I am currently working on an Angular 5 CRUD application, utilizing Google Firebase services. I have been following a helpful video tutorial on YouTube (link here), but I encountered this error ngOnInit() { var x = this.employeeService.getData(); x.sna ...

Exploring nested promises in TypeScript and Angular 2

I have a method called fallbackToLocalDBfileOrLocalStorageDB, which returns a promise and calls another method named getDBfileXHR, also returning a promise. In the code snippet provided, I am unsure whether I need to use 'resolve()' explicitly o ...

The component prop of Typography in TypeScript does not accept MUI styling

Working with MUI in typescript and attempting to utilize styled from MUI. Encountering an error when passing the component prop to the styled component. The typescript sandbox below displays the issue - any suggestions for a workaround? https://codesandbo ...

Ways to extend the default timeout duration in Angular

My server calls are taking a long time, around 30-40 minutes, and my Angular frontend is timing out. Is there a way to increase the default timeout for this service call? method1(id: number): Promise<number> { const body= JSON.stringify(id); ...

Retrieve the specific type of property from a generic data structure

I am currently working on a project where I need to determine the type of property within a given Type: type FooBarType { foo: string, bar: number } The function would be structured like this: getType<K extends keyof T>(key: K): string. The ...

The module "angular2-multiselect-dropdown" is experiencing a metadata version mismatch error

Recently, I updated the node module angular2-multiselect-dropdown from version v3.2.1 to v4.0.0. However, when running the angular build command, I encountered an "ERROR in Metadata version mismatch for module". Just to provide some context, I am using yar ...

Monitoring changes within the browser width with Angular 2 to automatically refresh the model

One of the challenges I faced in my Angular 2 application was implementing responsive design by adjusting styles based on browser window width. Below is a snippet of SCSS code showing how I achieved this: .content{ /*styles for narrow screens*/ @m ...

Converting Blob to File in Electron: A step-by-step guide

Is there a way to convert a Blob into a File object in ElectronJS? I attempted the following: return new File([blob], fileName, {lastModified: new Date().getTime(), type: blob.type}); However, it appears that ElectronJs handles the File object differently ...