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
andon
are fixed keywords that need to be nested as shownbuz*
are dynamic and will vary depending on the developer/project
You can find the Gist link and the code snippet on the TypeScript playground.