103

I know I can define string union types to restrict variables to one of the possible string values:

type MyType = 'first' | 'second'
let myVar:MyType = 'first'

I need to construct a type like that from constant strings, e.g:

const MY_CONSTANT = 'MY_CONSTANT'
const SOMETHING_ELSE = 'SOMETHING_ELSE'
type MyType = MY_CONSTANT | SOMETHING_ELSE

But for some reason it doesn't work; it says MY_CONSTANT refers to a value, but it being used as a type here.

Why does Typescript allow the first example, but doesn't allow the second case? I'm on Typescript 3.4.5

Can Poyrazoğlu
  • 33,241
  • 48
  • 191
  • 389
  • 2
    Sounds like what you really want is an [enum](http://www.typescriptlang.org/docs/handbook/enums.html)...? – T.J. Crowder May 22 '19 at 18:53
  • 5
    You want `type MyType = typeof MY_CONSTANT | typeof SOMETHING_ELSE`. There's a big difference between types (which exist only at design time) and values (which exist at runtime) – jcalz May 22 '19 at 18:53
  • I often throw [this answer](https://stackoverflow.com/a/50396312/2887218) at people when I sense confusion between types and values. – jcalz May 22 '19 at 18:56
  • @T.J.Crowder considered that, but I've got constant strings that are defined elsewhere that I want to use – Can Poyrazoğlu May 22 '19 at 20:17
  • @CanPoyrazoğlu - Fair enough, then Titian's answer is what you want. – T.J. Crowder May 23 '19 at 06:45

4 Answers4

138

To get the type of a variable you need to use the typeof type operator:

const MY_CONSTANT = 'MY_CONSTANT' // must be const, no annotation. let or var will not work
const SOMETHING_ELSE = 'SOMETHING_ELSE' // must be const, no annotation. let or var will not work
type MyType = typeof MY_CONSTANT | typeof SOMETHING_ELSE

Playground

Note:

Since there seems to be a lot of confusion when people use this. The const matters. If you use other types of declarations (let or var) the final type would be string. Only const preserves string literal types.

Note 2:

For this solution to work you must not specify any type annotation on the const, and let the compiler infer the type of the constants (ex this will not work :const MY_CONSTANT: string = 'MY_CONSTANT')

Titian Cernicova-Dragomir
  • 230,986
  • 31
  • 415
  • 357
  • yup, that worked. though I still don't get why I can create some string inline like `'test'` and use it directly, but need a `typeof` keyword if it's a reference. – Can Poyrazoğlu May 23 '19 at 08:55
  • 1
    @CanPoyrazoğlu 'test' is both a value and a type (a string literal type), by definition (just like a class is a value, the constructor, and a type, the instance type) – Titian Cernicova-Dragomir May 23 '19 at 08:57
  • 16
    `typeof MY_CONSTANT` will check if it is a whatever `string`, not specifically the `MY_CONSTANT` – Knut Holm Apr 13 '20 at 16:19
  • @knuhol not sure I understand what you mean here. `typeof MY_CONSTANT` will give you the type of the constant, whatever that is. If you defined it in such a way that the type of it is `string` it will continue to be a `string` – Titian Cernicova-Dragomir Apr 13 '20 at 18:37
  • 1
    If someone can explain the down-votes on this answer I would be grateful. I stand by the answer, it still does what it is supposed to and is an answer to the original question. If someone has issues with it let me know – Titian Cernicova-Dragomir Apr 17 '20 at 15:24
  • @knuhol see the note about `const` I think this is why you were getting `string` – Titian Cernicova-Dragomir Apr 30 '20 at 08:17
  • Titian - the problem with your solution is that ANY string is a valid MyType. – Jonas Rosenqvist Jul 14 '20 at 09:24
  • 4
    @JonasRosenqvist please read the comments and the notes. Please check the playground link associated. If you use **const** then the literal type is preserved and type checking works as you expect and not all strings are compatible. If you have a sample where this is not true I would like to see it. Sorry if I sound a bit mad, but this answer keeps getting downvotes for no reason and it's getting very annoying. Not sure what I can do to make it more clear **const** is required – Titian Cernicova-Dragomir Jul 14 '20 at 10:24
  • 2
    @TitianCernicova-Dragomir - you're right, I had made the mistake of declaring the constant as: `const MY_CONSTANT:string = 'MY_CONSTANT'` Specifying the type of constant made it accept any string. Sorry, you'll get my upvote! – Jonas Rosenqvist Jul 15 '20 at 11:05
  • @JonasRosenqvist That is important feedback, I'll add it in the notes for the next person reading it. Thank you. – Titian Cernicova-Dragomir Jul 15 '20 at 11:06
  • 1
    Important Note: It doesn't work when defining your strings with template literals. Then the types are just `string` – Andru Oct 23 '20 at 16:59
  • 1
    @Andru not true, as long as your string are constants, the delimiter does not matter. If you use interpolation then yes, they will be strings in 4.0: https://www.typescriptlang.org/play?#code/MYewdgzgLgBAsgTQPoGEDyA5AygFQIIY4wC8MABoqprgTmQFCiSxZpwCiOAEgJIYDiSdgBks7EuVYdufQSLEMoATwAOAU3hKcqjaWXqQAM3jJ02fIRgAfGPrVGYUzrwFDR7eo3DRHbZ7LcxKgwUPCJSMicZV3l2ABIAb0ozGkIAXzIYAHosmBksGB4CgCkAVVxHHAAlWU8AGzVYAA8ALk1tdQBueiaJACI+mGzctQAnUZBRnv7k6gscPqA – Titian Cernicova-Dragomir Oct 23 '20 at 17:28
  • @TitianCernicova-Dragomir Thanks for pointing that out. I indeed used a delimiter. I'm wondering why Typescript can't infer that the entire value will also be a string. – Andru Oct 24 '20 at 08:13
  • The problem with this that `Pick` will raise an error since Pick will try to extract the String prototypes rather than the actual strings – AlexanderD Oct 12 '21 at 10:25
  • Regarding `The const matters`; what about `let MY_CONSTANT: 'MY_CONSTANT'` (notice the colon)? – yakya Apr 15 '22 at 19:14
  • This seems to be the correct answer indeed and it includes crucial details on how to get this right. But it goes to show that bolting a proper type system onto a language that originally didn't have one (that is, creating TypeScript on top of JavaScript) can result in something ugly and overly complex. – Higemaru May 11 '22 at 09:36
20

You can also use enum for this case. For example:

// Define enum.
enum myConstants {
  MY_CONSTANT = 'my_constant',
  SMTH_ELSE = 'smth_else'
}

// Use it in an interface for typechecking.
interface MyInterface {
  myProp: myConstants
}

// Example of correct object - no errors.
let a: MyInterface = {
  myProp: myConstants.MY_CONSTANT
}

// Incorrect value - TS reports an error.
let b: MyInterface = {
  myProp: 'John Doe'
}

More about enums

Eugene
  • 903
  • 1
  • 9
  • 25
12

Enums cover the case quiet well:

export enum ITEM_TYPES {
    TYPE1 = 'text',
    TYPE2 = 'image'
}

export type IItemType = ITEM_TYPES.TYPE1 | ITEM_TYPES.TYPE2

And then in code ITEM_TYPES can be refered to for all kind of runtime comparisons:

if (type === ITEM_TYPES.TYPE1){
}
Mantas
  • 151
  • 1
  • 5
0

I made a post related to string literal union type. https://medium.com/@m.fatihalaziz/string-literal-union-type-with-typeguard-in-typescript-dff4c9741b4a

what it cover:

  • how to create a string literal union type
  • how to pass string as the string literal
  • how to use the type guard of typescript effectively

I hope it helps, Good Luck!

FatihAziz
  • 438
  • 4
  • 11