150

Consider following example.

enum DialogType {
    Options,
    Help
}

class Dialog { 
    test() : string {
        return "";
    }
}

class Greeter {

    openDialogs: { [key in DialogType]: Dialog | undefined } = {
        0: undefined,
        1: undefined
    };

    getDialog(t: DialogType) {
        return this.openDialogs[t];
    }
}

const greeter = new Greeter();
const d = greeter.getDialog(DialogType.Help);
if (d) document.write(d.test());

Also in playground

There are 3 issues/questions with it:

  1. Why I cannot omit properties in my initializer literal, even though I declare properties as '| undefined'
  2. Why I cannot use 'DialogType.Options' as type key, and have to use hardcoded number instead?
  3. Why do I have to use 'key in DialogType' instead of 'key: DialogType'? (Or can I? )
ironic
  • 8,368
  • 7
  • 35
  • 44
  • **See Also**: [Use Enum as restricted key type in Typescript](https://stackoverflow.com/q/44243060/1366033) – KyleMit Apr 06 '21 at 23:16

2 Answers2

265
  1. |undefined does not make a property optional, just means it can be undefined, there is a proposal to make |undefined members optional but currently it's not implemented. You need to use ? after ] to make all properties optional

    { [key in DialogType]?: Dialog }
    
  2. You can use the dialog enum values as keys, but they need to be computed properties:

    let openDialogs: { [key in DialogType]?: Dialog } = {
        [DialogType.Options]: undefined,
    };
    
  3. { [key: number or string]: Dialog } is an index signature. Index signatures are restricted to only number or string as the key type (not even a union of the two will work). So if you use an index signature you can index by any number or string (we can't restrict to only DialogType keys). The concept you are using here is called mapped types. Mapped types basically generate a new type based on a union of keys (in this case the members of DialogType enum) and a set of mapping rules. The type we created above is basically equivalent to:

    let o: { [DialogType.Help]?: Dialog; [DialogType.Options]?: Dialog; }
    
Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • 4
    That is brilliant, thank you so much! I was convinced that 'T?' was equivalent to 'T | undefined', especially after seeing this: https://www.typescriptlang.org/docs/handbook/advanced-types.html#optional-parameters-and-properties. So is optional type equivalent to something or is it kind of a thing on it's own? – ironic Oct 08 '18 at 11:11
  • 2
    @ironic It goes one way but not the other a property of type `T?` will be considered `T|undefined` for null checking, but a property of type `T|undefined` willnot be considered optional – Titian Cernicova-Dragomir Oct 08 '18 at 11:13
  • What id I wanted to specify a different member type for each key? something like : `let o: { [DialogType.Help]: DialogHelp; [DialogType.Options]: DialogOptions; }` – neomib Feb 12 '19 at 09:55
  • 1
    @neomib you can do it manually like in your example, if there is some mapping rule I would need to see it to offer a better solution – Titian Cernicova-Dragomir Feb 12 '19 at 09:57
  • Thanks! I realized that after commeting ;) – neomib Feb 12 '19 at 10:11
  • 2
    `key in` ended up being the _key_ part I was missing. ;) Thanks! – derpedy-doo Aug 08 '19 at 16:18
  • Interestingly when declaring this as a named `interface` - rather than in-line - I get a TS error (4.8.4): `A mapped type may not declare properties or methods.`. However, declaring it as a named `type` is totally fine. – Reece Daniels Nov 15 '22 at 16:21
  • Rather than specifying all available options (like this: "[DialogType.Help]: DialogHelp; [DialogType.Options]: DialogOptions;"), you can use this: "[key in DialogType]" - see my answer – rosell.dk Aug 19 '23 at 04:48
0

Here is an example of how to use enum as index in typescript:

enum DialogType {
    Options,
    Help,
    About
}
type Dialog = {
    whatever: string;
};
type DialogMap = { [key in DialogType]?: Dialog };

let o: DialogMap = {
    [ DialogType.Options ]: { whatever: "1" },
    [ DialogType.Help ]: { whatever: "2" }
};            
rosell.dk
  • 2,228
  • 25
  • 15