Reputation: 8893
TypeScript 2.8.4 in strict mode
I've got an enum like this:
export enum TabIndex {
Editor = 'editor',
Console = 'console',
Settings = 'settings',
Outputs = 'outputs'
}
Out of which I'm creating a Map like this:
tabs = new Map(<[string, string][]>Object.keys(TabIndex).map((key: any) => [TabIndex[key], key]));
Later on, I'm trying to get a particular member out of that Map using a query parameter which should be available in the Map:
const tabParam = this.urlSerializer.parse(this.location.path()).queryParams.tab; // this thign is either 'editor', 'console', 'settings', ... etc.
And then I do
if (this.tabs.has(tabParam)) { // at this point we know the tab is available
this.selectTab(TabIndex[this.tabs.get(tabParam)!]); // here, TS thinks the index may be undefined, that's why I'm using the non-null assertion operator "!"
}
This code still makes TS unhappy. It errors out with:
Element implicitly has an 'any' type because index expression is not of type 'number'.
And it is true, the index type is a string. But I know that and that's how it's supposed to be because enums support string values. Anybody an idea how to make TS happy here?
I did some research and this issue comment suggests this workaround using keyof typeof
:
const tabParam: keyof typeof TabIndex = this.urlSerializer.parse(this.location.path()).queryParams.tab;
This just makes TypeScript unhappy again:
Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'
Upvotes: 0
Views: 2166
Reputation: 4775
I think the problem is that you are giving your map the type:
Map<string, string>
. This is caused by the way you created your map: new Map(<[string, string][]> ...)
.
This is causing the error you are getting:
Type 'string' is not assignable to type '"Editor" | "Console" | "Settings" | "Outputs"'
In the next snippet, you try to use the value of that Map by calling the get
method with the key. This is however returning a string (as your Map's type has been defined as having values of type string.
this.selectTab(TabIndex[this.tabs.get(tabParam)!]);
The TabIndex[xxxx]
statement however expects that the 'xxxx' is one of the following values "Editor" | "Console" | "Settings" | "Outputs"
as you can deduct from the error message above.
To fix this, you need to change the type of your Map, so that typescript knows that the 'xxxx' in the snippet above will always be one of those values. To do that, you need to create a 'union types of those specific string literals'. Luckily, typescript gives us a way of extracting those from the TabIndex
definition.
keyof typeof TabIndex
This will resolve to the 'union type of string literals' that is needed to properly type the Map.
So to sum up, change the creation of the map to:
const tabs = new Map(<[string, keyof typeof TabIndex][]>Object.keys(TabIndex).map((key: any) => [TabIndex[key], key]));
This will make sure the values from the Map that can be passed to TabIndex[xxxx] will always be a known string literal.
Upvotes: 3
Reputation: 18292
The problem is that, in the line defining typeParam
, you are assigning a string
(queryParam.tab
is a string I imagine? Either that or any
), to a keyof typeof TabIndex
. string
can contain any string value, so it can't be assignable to a variable that only accepts four specific values.
But, if you are sure that tab
will always be what you expect you can make an assertion:
type TabIndexKey = keyof typeof TabIndex;
const tabParam: TabIndexKey = this.urlSerializer.parse(this.location.path()).queryParams.tab as TabIndexKey.
If queryParams.tab
is any
, the solution is the same.
EDIT: To avoid the error with not implicit any, index expressions for enums must use numbers, not strings. This means that:
TabIndex['editor']; // error with not implicit any
TabIndex[0]; // correct
So, I recommend you to change the definition of your enum and the tabs
map. Instead of assigning strings, assign numbers:
export enum TabIndex {
Editor = 0,
Console = 1,
Settings = 2,
Outputs = 3
}
const tabs = new Map(<[string, number][]>Object.keys(TabIndex).map((key: any) => [key, TabIndex[key]]));
Of course, by doing this we are lying a little, because tabs
is not really a <string, number>
map. In reality it has both strings and numbers. The number keys point to the property names, and the string keys point to their number equivalents. However, we can forget it, as you are going to use just the string keys.
Now, TabIndex[this.tabs.get(tabParam)!]
works correctly, as this.tabs.get
returns a number
.
Hope this helps you.
Upvotes: 0