blog.jakoblind.no

Your complete Typescript enum guide

Do you know when to use an enum instead of class, const, or type? And how to use it to avoid unexpected bugs?

This guide will help you be more confident when using enums in your Typescript code base.

Main takeaways:

  • Use Typescript enums when you have several options for something. For example SWEDISH, NORWEGIAN, ENGLISH
  • Use enums instead of “magical values” to make the code more readable and typesafe.
  • Enums have a value of type number or type string.

This guide is quite long. If you have a specific enum problem, here is a table of contents.

What is Typescript enum and when to use it?

Before we start, when should you use an enum? I think of it like this: When you have several options for something that cannot change.

The Typescript handbook on enums describes it like this: “Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases.”

An example could be languages:

enum Language {
    EN,
    SV,
    NO
}

As you can see, you create an enum with the enum keyword. Before we go deeper, we will look at some more examples. It’s the quickest way to “get” what an enum is.

Real-world examples of Typescript enums

I list some examples where enum could be suitable to use.

Log levels

enum LogLevel {
    ERROR,
    WARNING,
    INFO,
    DEBUG
}

Tracking events

enum EventAnalytics {
    PageLoad,
    Search,
    SubmitForm
}

HTTP status codes

enum HTTPStatus {
  OK = 200,
  Redirect = 301,
  BadRequest = 400,
  Unauthorized = 401,
  NotFound = 404,
  InternalServerError = 500,
}

Usage of enums.

Ok, now we know how to define an enum. What can we do with it?

Let’s look at the LogLevel enum. You can use it to send it to a log function to specify which log level you want:

log(LogLevel.DEBUG, "This is a debug log")

Let’s take a closer look on how that log function could be implemented.

function log(log: LogLevel, msg: string) {
  if (log === LogLevel.ERROR) {
    console.error(msg)
  } else {
    console.log(msg)
  }
}

As you can see, LogLevel can be used as any type of function argument. And you can compare it with ===.

The value of a Typescript enum

Typescript enums can be either of type number or string. Let’s first have a look at enums of type number.

When you create an enum as we did in the previous examples, it automatically gets a value starting at 0.

enum LogLevel {
    ERROR, // the value is 0
    WARNING, // the value is 1
    INFO, // the value is 2
    DEBUG // the value is 3
}

You can check if I’m telling the truth by console logging it

console.log(LogLevel.ERROR === 0) // will output true

You can try it out in the Typescript playground in the browser. You can use this for other examples in this article as well!

It’s possible to override the default value like this:

enum EventAnalytics {
    PageLoad = 1,
    Search = 10,
    SubmitForm = 102
}

If you only give the first enum a value, the others will autoincrement from there:

enum EventAnalytics {
    PageLoad = 1,
    Search, // the value is 2
    SubmitForm // the value is 3
}

If you want to get the value of an enum by key, you can reference the key like this:

console.log(EventAnalytics.PageLoad) // prints out 1

This converts the enum to the int value.

Typescript enum with a string value

It’s also possible to assign a string value to an enum

enum Language {
    EN = "EN",
    SV = "SV",
    NO = "NO"
}

There are two good reasons why you might want to use string enums.

The first one is that it could be easier to debug runtime because you’ll see “EN” instead of 0. It’s quicker to figure out the meaning of “EN” than 0.

The second reason is if you want to compare the enum with a string value which we’ll talk about next.

Typescript enums from string literal

Sometimes you get a string value from the “outside” and you want to compare it to an enum.

Let’s say for example you are implementing a REST endpoint and you get the language as the parameter. Because you cannot send typescript enums over HTTP, you might get a string value instead.

You can then compare a string value with your language enum:

const langString = "EN" // we get this from for example a REST endpoint
console.log(langString === Language.en) // will output true

When doing a comparison it automatically converts it to the string value.

Make a reverse lookup to get the key by value

You can also do a reverse lookup of the Typescript enum like this:

const langString = "EN" // we get this from for example a REST endpoint
Language[langString] // this gives us the Language.en enum

Typescript enum vs string literal

Now you might be thinking to yourself, why do we even need enums? Wouldn’t it be easier to write something like this if you want to check if a string is “EN”?

console.log(langString === "EN")

If you write it like this, you lose readability and type safety. Let me explain.

Using a constant string value like this is also called “magic value” and is an anti-pattern in programming. There are several reasons for that.

First, it’s easy to slip in subtle errors when using magical values. You could for example accidentally write “SV” instead of “SE” when you intend to write the Swedish language code. The error will not be picked up until run time.

Secondly, it’s hard to know the intent behind the value. What does “EN” mean? Most likely a language code. But “EN” could also mean a unit of width in typography, a letter in the Cyrillic alphabet, or “EuroNews”. Reading Language.EN removes this ambiguity.

Typescript get enum from number

You can also do the reverse lookup if you have an enum with number values. However, the return value is a bit different than when you have an enum of type string. Let’s have a look.

enum Fruits {
    banana,
    orange,
    apple
}

const number = 1
Fruits[1] // returns "banana"

Note here that the return value is a string representation of the enum, and not the enum itself.

How to check if Typescript enum value exists

Let’s say you want to check if an enum contains or includes a certain value.

Then we can use the reverse lookup trick that we just learned to check if a value exists. If it doesn’t exist, it will return undefined.

if (Language["DK"]) {
  console.log("DK was defined!")
}

Iterate through a typescript enum to get all values

You can also iterate through all possible enum values. The cleanest way to do it is with a for loop.

enum Fruits {
    banana,
    orange,
    apple
}
for (const f in Fruits) {
    console.log(f)
}

The output of this might surprise you (it sure did surprise me).

0
1
2
banana
orange
apple

First, it outputs the value of each enum, then it outputs all string representations of all enums. Not sure why, but that’s just how it is. If you want only the numbers, you have to filter out the non-numbers:

enum Fruits {
    banana,
    orange,
    apple
}
for (const f in Fruits) {
    if (!isNaN(Number(f))) {
        console.log(f)
    }
}

Now, this returns only the string values:

banana
orange
apple

tags: