50

Is it currently possible to implement an indexer on a class in TypeScript?

class MyCollection {
   [name: string]: MyType;       
}

This doesn't compile. I can specify an indexer on an interface, of course, but I need methods on this type as well as the indexer, so an interface won't suffice.

Thanks.

MgSam
  • 12,139
  • 19
  • 64
  • 95
  • 1
    No: see http://typescript.codeplex.com/discussions/398379 and http://stackoverflow.com/questions/14791550/how-to-implement-array-signature-method-in-typescript and – MiMo Feb 12 '13 at 22:06
  • 2
    The code above is now legal and compiles in the latest versions of TypeScript – Rich N Nov 22 '18 at 11:40
  • @RichN but then you can't add methods – Eloff Sep 10 '20 at 18:17

4 Answers4

41

You cannot implement a class with an indexer. You can create an interface, but that interface cannot be implemented by a class. It can be implemented in plain JavaScript, and you can specify functions as well as the indexer on the interface:

class MyType {
    constructor(public someVal: string) {

    }
}

interface MyCollection {   
   [name: string]: MyType;
}

var collection: MyCollection = {};

collection['First'] = new MyType('Val');
collection['Second'] = new MyType('Another');

var a = collection['First'];

alert(a.someVal);
Fenton
  • 241,084
  • 71
  • 387
  • 401
15

This is an old question, for those looking for the answer: now it's possible to define a indexed property like:

let lookup : {[key:string]:AnyType};

the signature of the key must be either string or integer see:

Interfaces on www.typescriptlang.org

Arjan
  • 1,034
  • 8
  • 29
  • 3
    The question is about classes, not interfaces. And if you add such an indexer on a class, it can no longer have normal methods that don't return an `AnyType`. This answer is not correct for the question asked. What the question asks for is still not possible. – MgSam Oct 04 '21 at 19:46
8

Is not possible to define an indexed property getter/setter in a class but you can "simulate" that in a way like this using Proxy:

class IndexedPropSample  {
  [name: string | symbol]: any;

  private static indexedHandler: ProxyHandler<IndexedPropSample> = {
    get(target, property) {
      return target[property];
    },
    set(target, property, value): boolean {
        target[property] = value;
        return true;
    }
  };

  constructor() {
      return new Proxy(this, IndexedPropSample.indexedHandler);
  }

  readIndexedProp = (prop: string | symbol): any => {
      return this[prop];
  }

}


var test = new IndexedPropSample();

test["propCustom"] = "valueCustom";

console.log(test["propCustom"]); // "valueCustom"
console.log(test.readIndexedProp("propCustom")); // "valueCustom"
console.log(test instanceof IndexedPropSample); // true
console.log(Object.keys(test)); // ["propCustom", "readIndexedProp"]

you can try it in Typescript Playground

Dardino
  • 154
  • 1
  • 7
0

@Dardino: Thant helped a lot. Thanks. In case anybody also needs to do function calls and access properties through the proxy, here is some code based on Dardinos's one.

  private static indexedHandler: ProxyHandler<IndexedPropSample> = {
    get(target, prop) {
      if (typeof prop === "string" && !(typeof target[prop as string])) {
        // Array access
        return target.getItem(prop);
      }
      else if ((typeof prop === "string" && typeof target[prop as string] === "function")) {
        // function call
        return target[prop].bind(target);
      } else if (typeof prop === "string") {
        // property access
        return target[prop];
      }

      return undefined;
    },
    set(target, prop, value): boolean {
      if (typeof prop === "string" && !(typeof target[prop as string])) {
        // Array access
        target.setItem(prop, value);
        return true;
      } else if (typeof prop === "string") {
        // property access
        return target[prop] = value;
      }
      return false;
    }
  };