Typescript index signature and methods

Any properties or methods declared in the class or interface with index signature must have type compatible with what you have in the index. That's why adding Function to the index signature helps.

The reason is explained in the documentation:

While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name’s type does not match the string index’s type, and the type-checker gives an error:

interface NumberDictionary {
    [index: string]: number;
    length: number;    // ok, length is a number
    name: string;      // error, the type of 'name' is not a subtype of the indexer
}

Adding any to the indexer signature can be considered a bad practice, because any suppress typechecking and any value obtained in any way from any also has any type unless explicitly declared otherwise, so having any increases chances that you have type errors which are not reported by the compiler.

Adding Function to the type is better, because it correctly describes the actual data contained in the class - when you use indexed access to get a value like this

  const key = 'greeting';
  const value = this[key];

you might get a function as a value if key happens to be equal to 'greet'. Also, when you assign string value to greet:

  this['greet'] = 'hi!';

the method will be overwritten with a string value and you won't be able to call it any more.

Considering all that, it's better to keep dictionary with index signature in separate property of the class, not in the class itself. Something like this could work:

class Greeter {
    data: { [key: string]: string | number[] } = {};

    get greeting(): string { return this.data.greeting.toString() }
    set greeting(value: string) { this.data.greeting = value };

    constructor(message: string) {
        this.greeting = message;
    }
    greet(): string {
        return "Hello, " + this.greeting;
    }
}

Tags:

Typescript