1

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.

4
  • This should probably be a function that you can call i.e. class Child extends Parent(...) {, but it's unsafe anyways because you could accidentally override a built-in or inherited method D: Commented Mar 15, 2022 at 18:17
  • 1
    "that is [not] making instances of the class, extensible" No, it makes a CanBeExtended extensible, which wprls if you widen a MyClass instance to CanBeExtended (e.g., const x: CanBeExtended = new MyClass([])). An implements class is just a check, it doesn't affect the typing. If you want MyClass instances to be treated like that you can put an index signature in the class itself, like this. This might be what you want, but this is almost like turning off type safety within MyClass completely. Seems like what you're asking for, though. 🤷‍♂️ Commented Mar 15, 2022 at 18:18
  • That's great @jcalz. This use case is within a component-library, and only applies to an internal tool, so type safety doesn't need to be airtight. Works like a charm. If you add that as an answer, I'd love to select it. Commented Mar 15, 2022 at 18:36
  • I will write up an answer when I get a chance. Aside: I just read my previous comment, and "wprls" is supposed to be "works" which I guess means my right hand was not properly placed on the keyboard, or maybe something short-circuited in my brain. Oh well, to err is hrmxn! Commented Mar 15, 2022 at 18:41

1 Answer 1

1

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.

Playground link to code

Sign up to request clarification or add additional context in comments.

4 Comments

Maybe use a generic parameter instead of string for the keys
argh, you can't… would've been too easy
@Bergi yeah there are definitely other approaches... but this is the answer to the question as asked. I suppose I could go into more detail about why an index signature with any isn't necessarily something people want to do in general
@jcalz - Is there some method of having the key be a generic type (eg, one of the elements of propList), rather than just string? Attempting to improve on this, I ran into the same issue as @Bergi did in the comment above

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.