Issue
Using TypeScript, I’d like to create a class instances of which have a number of methods, but can be initialized to automatically add several other methods, based off of one of the predefined ones. The base class has a props
method, which takes an object, and does stuff with it:
class MyClass {
props(props: Record<string, any>) {
// Do something with passed props
}
}
I want to be able to pass in a list of method names on construction that are then used to dynamically add class instance methods which use this props
method to do something. So:
constructor(propList: Array<string> = []) {
propList.forEach(propName => {
// Don't allow overwriting of existing methods
if (this[propName]) {
throw new Error(
`Cannot create prop setter for '${propName}'. Method with that name already exists!`
);
} else {
this[propName] = (value: any) => {
this.props({ [propName]: value });
};
}
});
}
When I try to do so, TS shows an error:
Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'MyClass'. No index signature with a parameter of type 'string' was found on type 'MyClass'.ts(7053)
Is there a way to do this? Saw this semi-related question, but didn’t feel applicable. Thanks! New to TS.
EDIT
I tried having the class implement an open interface:
interface CanBeExtended {
[key: string]: any;
}
class MyClass implements CanBeExtended {
// ...
but I think that is making the class itself, but not instances of the class, extensible. At any rate, it is not fixing the issue.
Solution
You can declare an index signature in a class
, as long as it doesn’t conflict with the types any other members (so if you have a props()
method, you can’t make the index signature [k: string]: number
, for example, since props
is a method and not a number
). In your case you want to use the any
type, which will definitely not conflict (although it’s also not very type safe). So it looks like this:
class MyClass {
[k: string]: any; // index signature
constructor(propList: Array<string> = []) {
propList.forEach(propName => {
// Don't allow overwriting of existing methods
if (this[propName]) { // no error here anymore
throw new Error(
`Cannot create prop setter for '${propName}'. Method with that name already exists!`
);
} else {
this[propName] = (value: any) => { // no error here anymore
this.props({ [propName]: value });
};
}
});
}
props(props: Record<string, any>) {
// Do something with passed props
}
}
And your errors go away.
Answered By – jcalz
Answer Checked By – Dawn Plyler (BugsFixing Volunteer)