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

Can you identify the category of the new Set containing the elements 1, 2, and 3?

As a beginner in TypeScript, I'm currently exploring the appropriate type for JavaScript's new Set([1, 2, 3]), but my search has been unsuccessful so far. For instance: const objNums: {[key: string]: number} = {one: 1, two: 2, three: 3}; const a ...

Alter the entity type when passing it as a parameter

Trying to alter the Entity type, I am invoking a function that requires two parameters - one being the entity and the other a location. However, when trying to assign a Type for the first entity, it throws an error: Error: Argument of type 'Node<En ...

Encountering an error while using TypeScript, Mocha, and Express: "TypeError: app.address is not a

While transitioning an API from ES6 to TypeScript, a particular issue arises when attempting to run unit tests on the Express REST Endpoints: TypeError: Cannot read property 'address' of undefined The server code has been slightly adjusted for ...

Having trouble with ngx-pagination's next page button not responding when clicked?

I am experiencing issues with pagination. The next page button does not function as expected, and clicking on the page number also does not work. Below is the code snippet and a Demo link for your reference. HTML <table mat-table [dataSou ...

Display your StencilJs component in a separate browser window

Looking for a solution to render a chat widget created with stenciljs in a new window using window.open. When the widget icon is clicked, a new window should open displaying the current state while navigating on the website, retaining the styles and functi ...

TypeScript/Javascript - Error: The specified function is not callable

After recently delving into TypeScript, I found myself encountering an error in my code for a wheel mini-game on my app. The specific error being displayed in the console is: this.easeOut is not a function The relevant portion of the code causing the iss ...

Electron triggers MouseLeave event on child elements

Dealing with mouse hover events can be a bit tricky, especially when working with AngularJS in an Electron-hosted app. Here's the HTML template and script I'm using: HTML: <div id="controlArea" (mouseenter) = "onControlAreaEnter()" ...

What sets apart the utilization of add versus finalize in rxjs?

It appears that both of these code snippets achieve the same outcome: Add this.test$.pipe(take(1)).subscribe().add(() => console.log('added')); Finalize this.test$.pipe(take(1), finalize(() => console.log('finalized'))).sub ...

Delay the execution until all promises inside the for loop are resolved in Angular 7 using Typescript

I am currently working on a project using Angular 7. I have a function that contains a promise which saves the result in an array as shown below: appendImage(item){ this.imageCompress.compressFile(item, 50, 50).then( result => { this.compressedI ...

Searching is disrupted when the page is refreshed in NextJS

When I refresh the page, router.query.title disappears. I have read that I need to use getServerSideProps, but I'm unsure of what to include in the function. Can anyone provide guidance on how to resolve this issue? Update: I followed Yilmaz's s ...

In Rxjs, ConcatMap doesn't get executed

I am currently developing a file upload component that involves checking for the existence of a file before proceeding with the upload process. If the file already exists, the user is given the option to either save as a copy or overwrite the existing file ...

How to correctly type socket events when developing a customized useSocket hook in TypeScript?

Both my socket server and socket client are set to listen for a specific range of events. Below are the definitions for these socket events: import { Server } from "socket.io"; import { Socket } from "socket.io-client"; import { Disconn ...

Please indicate the Extended class type in the return of the child Class method using TypeScript 2.4

I'm currently in the process of upgrading from TypeScript version 2.3.2 to 2.4.2. In the previous version (2.3), this piece of code functioned without any issues: class Records { public save(): Records { return this; } } class User extends ...

Retrieving information from a .json file using TypeScript

I am facing an issue with my Angular application. I have successfully loaded a .json file into the application, but getting stuck on accessing the data within the file. I previously asked about this problem but realized that I need help in specifically und ...

NextJS function utilizes its own references within React and automatically fetches data without the need for an import

I recently purchased this template and I'm trying to figure out which page the code snippet "{getLayout(<Component {...pageProps} />)}" is directed to during the initial load. It seems like it might be a global variable hidden somewher ...

Showing canvas lines while dragging (using only plain JavaScript, with React.JS if needed)

Is there a way to add lines with two clicks and have them visible while moving the mouse? The line should only be drawn when clicking the left mouse button. Any suggestions on how I can modify my code to achieve this functionality? Currently, the lines are ...

What is the equivalent of getElementById in .ts when working with tags in .js?

Looking to incorporate Electron, Preload, Renderer with ReactJS and TypeScript into my project. <index.html> <body> <div id="root" /> <script src='./renderer.js'/> </body> <index.ts> const root = Re ...

Typescript not flagging an error for an object being declared without a type

I am encountering an issue with my tsconfig.json file: { "compilerOptions": { "allowJs": true, "allowSyntheticDefaultImports": true, "baseUrl": "src", "isolatedModules": true, "jsx": "preserve", "esModuleInterop": true, "forc ...

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

Determine whether the radio button has been selected

Within my HTML code, there's a radio button enclosed in a form: <mat-radio-button [(ngModel)]="boxChecked" name="boxChecked" value="boxChecked">Check me</mat-radio-button> In the TypeScript section, I've declared my boolean variable ...