Software Engineer - Lindy.ai
The author selected the COVID-19 Relief Fund to receive a donation as part of the Write for DOnations program.
In TypeScript, enums, or enumerated types, are data structures of constant length that hold a set of constant values. Each of these constant values is known as a member of the enum. Enums are useful when setting properties or values that can only be a certain number of possible values. One common example is the suit value of a single card in a deck of playing cards. Every card that is drawn will either be a club, a diamond, a heart, or a spade; there are no possible suit values beyond these four, and these possible values are not likely to change. Because of this, an enum would be an efficient and clear way to describe the possible suits of a card.
Whereas most features of TypeScript are useful for throwing errors during compilation, enums are also useful as data structures that can hold constants for your code. TypeScript translates enums into JavaScript objects in the final code emitted by the compiler. Because of this, you can use enums to make a codebase more readable, as you can have multiple constant values grouped in the same data structure, while also making the code more type-safe than just having different const
variables laying around.
This tutorial will explain the syntax used to create enum types, the JavaScript code that the TypeScript compiler creates under the hood, how to extract the enum object type, and a use case for enums that involves bit flags in game development.
To follow this tutorial, you will need:
tsc
) installed on your machine. To do this, refer to the official TypeScript website.All examples shown in this tutorial were created using TypeScript version 4.2.3.
In this section, you will run through an example of declaring both a numeric enum and a string enum.
Enums in TypeScript are usually used to represent a determined number of options for a given value. This data is arranged in a set of key/value pairs. While the keys must be strings, as with JavaScript objects in general, the values for enum members are often auto-incremented numbers that mainly serve to distinguish one member from the other. Enums with only number values are called numeric enums.
To create a numeric enum, use the enum
keyword, followed by the name of the enum. Then create a curly bracket ({}
) block, where you will specify the enum members inside, like this:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
In this example, you are making an enum called CardinalDirection
, which has a member that represents each of the cardinal directions. An enum is an appropriate choice of data structure to hold these options, since there are always only four options for values: north, south, east, and west.
You used the number 1
as the value of the first member of your CardinalDirection
enum. This assigns the number 1
to be the value of North
. However, you did not assign values to the other members. This is because TypeScript automatically sets the remaining members to the value of the previous member plus one. CardinalDirection.East
would have the value 2
, CardinalDirection.South
would have the value 3
, and CardinalDirection.West
would have the value 4
.
This behavior only works with numeric enums that have only number values for each member.
You can also completely ignore setting the value of the enum members:
enum CardinalDirection {
North,
East,
South,
West,
};
In this case, TypeScript is going to set the first member to 0
, and then set the other ones automatically based on that one, incrementing each by one. This will result in code identical to the following:
enum CardinalDirection {
North = 0,
East = 1,
South = 2,
West = 3,
};
The TypeScript compiler defaults to assigning numbers to enum members, but you can override this to make a string enum. These are enums that have string values for each member; these are useful when the value needs to carry a certain human-readable meaning, such as if you’ll need to read the value in a log or error message later on.
You can declare enum members to have string values with the following code:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W'
}
Now each of the directions has a letter value that indicates which direction they are tied to.
With the declaration syntax covered, you can now check out the underlying JavaScript to learn more about how enums behave, including the bi-directional nature of the key/value pairs.
Upon TypeScript compilation, enums are translated into JavaScript objects. However, there are a few features of enums that differentiate them from objects. They offer a more stable data structure for storing constant members than traditional JavaScript objects, and also offer bi-directional referencing for enum members. To show how this works, this section will show you how TypeScript compiles enums in your final code.
Take the string enum you created in the last section:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
This becomes the following code when compiled to JavaScript using the TypeScript compiler:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection["North"] = "N";
CardinalDirection["East"] = "E";
CardinalDirection["South"] = "S";
CardinalDirection["West"] = "W";
})(CardinalDirection || (CardinalDirection = {}));
In this code, the "use strict"
string starts strict mode, a more restrictive version of JavaScript. After that, TypeScript creates a variable CardinalDirection
with no value. The code then contains an immediately invoked function expression (IIFE) that takes the CardinalDirection
variable as an argument, while also setting its value to an empty object ({}
) if it has not already been set.
Inside the function, once CardinalDirection
is set as an empty object, the code then assigns multiples properties to that object:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection["North"] = "N";
CardinalDirection["East"] = "E";
CardinalDirection["South"] = "S";
CardinalDirection["West"] = "W";
})(CardinalDirection || (CardinalDirection = {}));
Notice that each property is one member of your original enum, with the values set to the enum’s member value.
For string enums, this is the end of the process. But next you will try the same thing with the numeric enum from the last section:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
This will result in the following code, with the highlighted sections added:
"use strict";
var CardinalDirection;
(function (CardinalDirection) {
CardinalDirection[CardinalDirection["North"] = 1] = "North";
CardinalDirection[CardinalDirection["East"] = 2] = "East";
CardinalDirection[CardinalDirection["South"] = 3] = "South";
CardinalDirection[CardinalDirection["West"] = 4] = "West";
})(CardinalDirection || (CardinalDirection = {}));
In addition to each member of the enum becoming a property of the object (CardinalDirection["North"] = 1]
), the enum also creates a key for each number and assigns the string as the value. In the case of North
, CardinalDirection["North"] = 1
returns the value 1
, and CardinalDirection[1] = "North"
assigns the value "North"
to the key "1"
.
This allows for a bi-directional relationship between the names of the numeric members and their values. To test this out, log the following:
console.log(CardinalDirection.North)
This will return the value of the "North"
key:
Output1
Next, run the following code to reverse the direction of the reference:
console.log(CardinalDirection[1])
The output will be:
Output"North"
To illustrate the final object that represents the enum, log the entire enum to the console:
console.log(CardinalDirection)
This will show both of the sets of key/value pairs that create the bi-directionality effect:
Output{
"1": "North",
"2": "East",
"3": "South",
"4": "West",
"North": 1,
"East": 2,
"South": 3,
"West": 4
}
With an understanding of how enums work under the hood in TypeScript, you will now move on to using enums to declare types in your code.
In this section, you will try out the basic syntax of assigning enum members as types in your TypeScript code. This can be done in the same way that basic types are declared.
To use your CardinalDirection
enum as the type of a variable in TypeScript, you can use the enum name, as shown in the following highlighted code:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const direction: CardinalDirection = CardinalDirection.North;
Notice that you are setting the variable to have the enum as its type:
const direction: CardinalDirection = CardinalDirection.North;
You are also setting the variable value to be one of the members of the enum, in this case CardinalDirection.North
. You can do this because enums are compiled to JavaScript objects, so they also have a value representation in addition to being types.
If you pass a value that is not compatible with the enum type of your direction
variable, like this:
const direction: CardinalDirection = false;
The TypeScript compiler is going to display the error 2322
:
OutputType 'false' is not assignable to type 'CardinalDirection'. (2322)
direction
can therefore only be set to a member of the CardinalDirection
enum.
You are also able to set the type of your variable to a specific enum member:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const direction: CardinalDirection.North = CardinalDirection.North;
In this case, the variable can only be assigned to the North
member of the CardinalDirection
enum.
If the members of your enum have numeric values, you can also set the value of your variable to those numeric values. For example, given the enum:
enum CardinalDirection {
North = 1,
East,
South,
West,
};
You can set the value of a variable of type CardinalDirection
to 1
:
const direction: CardinalDirection = 1;
This is possible because 1
is the value of the North
member of your CardinalDirection
enum. This only works for numeric members of the enum, and it relies on the bi-directional relationship the compiled JavaScript has for numeric enum members, covered in the last section.
Now that you have tried out declaring variable types with enum values, the next section will demonstrate a specific way of manipulating enums: extracting the underlying object type.
In the previous sections, you found that enums are not just a type-level extension on top of JavaScript, but have real values. This also means that the enum data structure itself has a type, which you will have to take into account if you are trying to set a JavaScript object that represents an instance of the enum. To do this, you will need to extract the type of the enum object representation itself.
Given your CardinalDirection
enum:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
Try to create an object that matches your enum, like the following:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const test1: CardinalDirection = {
North: CardinalDirection.North,
East: CardinalDirection.East,
South: CardinalDirection.South,
West: CardinalDirection.West,
}
In this code, test1
is an object with type CardinalDirection
, and the object value includes all the members of the enum. However, the TypeScript compiler is going to show the error 2322
:
OutputType '{ North: CardinalDirection; East: CardinalDirection; South: CardinalDirection; West: CardinalDirection; }' is not assignable to type 'CardinalDirection'.
The reason for this error is that the CardinalDirection
type represents a union type of all the enum members, not the type of the enum object itself. You can extract the object type by using typeof
before the name of the enum. Check the highlighted code below:
enum CardinalDirection {
North = 'N',
East = 'E',
South = 'S',
West = 'W',
};
const test1: typeof CardinalDirection = {
North: CardinalDirection.North,
East: CardinalDirection.East,
South: CardinalDirection.South,
West: CardinalDirection.West,
}
The TypeScript compiler will now be able to compile your code correctly.
This section showed a specific way to widen your use of enums. Next, you will work through a use case in which enums are applicable: bit flags in game development.
In this last section of the tutorial, you’ll run through a tangible use case for enums in TypeScript: bit flags.
Bit flags are a way to represent different boolean-like options into a single variable, by using bitwise operations. For this to work, each flag must use exactly one bit of a 32-bit number, as this is the max value allowed by JavaScript when doing bitwise operations. The max 32-bit number is 2,147,483,647
, which in binary is 1111111111111111111111111111111
, so you have 31
possible flags.
Imagine you are building a game, and the player may have different skills, like SKILL_A
, SKILL_B
, and SKILL_C
. To make sure your program knows when a player has a certain skill, you can make flags that can be turned on or off, depending on the player’s status.
With the following pseudocode, give each skill flag a binary value:
SKILL_A = 0000000000000000000000000000001
SKILL_B = 0000000000000000000000000000010
SKILL_C = 0000000000000000000000000000100
You can now store all the current skills of the player in a single variable, by using the bitwise operator |
(OR):
playerSkills = SKILL_A | SKILL_B
In this case, assigning a player the bit flag 0000000000000000000000000000001
and the bit flag 0000000000000000000000000000010
with the |
operator will yield 0000000000000000000000000000011
, which will represent the player having both skills.
You are also able to add more skills:
playerSkills |= SKILL_C
This will yield 0000000000000000000000000000111
to indicate that the player has all three skills.
You can also remove a skill using a combination of the bitwise operators &
(AND) and ~
(NOT):
playerSkills &= ~SKILL_C
Then to check if the player has a specific skill, you use the bitwise operator &
(AND):
hasSkillC = (playerSkills & SKILL_C) == SKILL_C
If the player does not have the SKILL_C
skill, the (playerSkills & SKILL_C)
part is going to evaluate to 0
. Otherwise (playerSkills & SKILL_C)
evaluates to the exact value of the skill you are testing, which in this case is SKILL_C
(0000000000000000000000000000010
). This way you can test that the evaluated value is the same as the value of the skill you are testing it against.
As TypeScript allows you to set the value of enum members to integers, you can store those flags as an enum:
enum PlayerSkills {
SkillA = 0b0000000000000000000000000000001,
SkillB = 0b0000000000000000000000000000010,
SkillC = 0b0000000000000000000000000000100,
SkillD = 0b0000000000000000000000000001000,
};
You can use the prefix 0b
to represent binary numbers directly. If you do not want to use such big binary representations, you can use the bitwise operator <<
(left shift):
enum PlayerSkills {
SkillA = 1 << 0,
SkillB = 1 << 1,
SkillC = 1 << 2,
SkillD = 1 << 3,
};
1 << 0
will evaluate to 0b0000000000000000000000000000001
, 1 << 1
to 0b0000000000000000000000000000010
, 1 << 2
to 0b0000000000000000000000000000100
, and 1 << 3
to 0b0000000000000000000000000001000
.
Now you can declare your playerSkills
variable like this:
let playerSkills: PlayerSkills = PlayerSkills.SkillA | PlayerSkills.SkillB;
Note: You must explicitly set the type of the playerSkills
variable to be PlayerSkills
, otherwise TypeScript will infer it to be of type number
.
To add more skills, you would use the following syntax:
playerSkills |= PlayerSkills.SkillC;
You can also remove a skill:
playerSkills &= ~PlayerSkills.SkillC;
Finally, you can check if the player has any given skill using your enum:
const hasSkillC = (playerSkills & PlayerSkills.SkillC) === PlayerSkills.SkillC;
While still using bit flags under the hood, this solution provides a more readable and organized way to display the data. It also makes your code more type-safe by storing the binary values as constants in an enum, and throwing errors if the playerSkills
variable does not match a bit flag.
Enums are a common data structure in most languages that provide a type system, and this is no different in TypeScript. In this tutorial, you created and used enums in TypeScript, while also going through a few more advanced scenarios, such as extracting the object type of an enum and using bit flags. With enums, you can make your code base more readable, while also organizing constants into a data structure rather than leaving them in the global space.
For more tutorials on TypeScript, check out our How To Code in TypeScript series page.
Thanks for learning with the DigitalOcean Community. Check out our offerings for compute, storage, networking, and managed databases.
TypeScript is an extension of the JavaScript language that uses JavaScript’s runtime with a compile-time type checker. This combination allows developers to use the full JavaScript ecosystem and language features, while also adding optional static type-checking, enum data types, classes, and interfaces.
This series will show you the syntax you need to get started with TypeScript, allowing you to leverage its typing system to make scalable, enterprise-grade code.
This textbox defaults to using Markdown to format your answer.
You can type !ref in this text area to quickly search our full set of tutorials, documentation & marketplace offerings and insert the link!