[SOLVED] TypeScript – Allowing arbitrary extensions to class instance

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.

Playground link to code

Answered By – jcalz

Answer Checked By – Dawn Plyler (BugsFixing Volunteer)

Leave a Reply

Your email address will not be published. Required fields are marked *