TypeScript provides the enum
keyword to define a set of labeled values. This can be a set of string
or number
values. However, it is recommended not to create an enum of mixed values (of these types). Let’s see a small example.
In the above example, we have defined the enum Speed
that contains SLOW
, MEDIUM
and FAST
members. We have set the value of SLOW
to 1
and subsequent members get the incremented value of the previous member implicitly. If you do not set the value of the first member, its implicit value will be 0
and later members will be incremented in a similar fashion.
You are free to set custom numeric values to each member or only some of them. Those members whose value is not explicitly provided will be auto-incremented by looking at the previous member value. Enums in TypeScript isn’t only a compile-time feature. The enum type does actually gets compiled into a JavaScript object.
This program when compiled produces the following output.
// define an enum
var Speed;
(function (Speed) {
Speed[(Speed["SLOW"] = 0)] = "SLOW";
Speed[(Speed["MEDIUM"] = 1)] = "MEDIUM";
Speed[(Speed["FAST"] = 2)] = "FAST";
})(Speed || (Speed = {}));
console.log(Speed);
As you can see from the above example, Speed
is actually a variable that we can reference in JavaScript and it is an object
at runtime. We have member names (labels) as keys in this object with their respective values in the enum.
We also get a reverse mapping of keys and values in this object. This reverse mapping is useful when you want to access the member name (label) of the enum using a value. This is explained in the following example.
The most preferred way to use enums is with string
values. Though string
enums do not have the feature of auto-incrementing values (since string value can not increment), they provide good visual aid while debugging since their member values are more verbose than the integer.
In the above example, we have Speed
enum but with string
values. In the case of string
members, the output enum object does not have reverse mapping as you can see from the result.
Constant Enums
So far we have seen that enums get injected into the compiled JavaScript code as plain JavaScript objects. Wherever we use the enum member reference in the source code, that reference will point to this generated object in the output code. Let’s take a simple example.
In the above example, we have the Speed enum with some string
values. The racer
object’s speed
property has the value of Speed.MEDIUM
member. If we compile this code, the JavaScript output will look like this.
// enum-non-const.js
// define a non-constant enum
var Speed;
(function (Speed) {
Speed["SLOW"] = "slow";
Speed["MEDIUM"] = "medium";
Speed["FAST"] = "fast";
})(Speed || (Speed = {}));
// define a simple object
var racer = {
name: "Ross Geller",
speed: Speed.MEDIUM,
};
As you can see, the JavaScript output contains the Speed
object and speed
property of the racer
object points to the MEDIUM
value of this object.
This sometimes adds unnecessarily overhead if you aren’t necessarily doing anything with this object. In some cases, instead of having reference to the output object, you want the enum member value to be inline.
If we put const
keyword before the enum declaration, the TypeScript compiler will substitute the value of the member reference in place.
In the above example, we have the same enum Speed
we used before but now it’s a constant enum since we added the const
keyword before it. This time, the speed
property will get the 'medium'
literal value instead of Speed.MEDIUM
expression in the compiled JavaScript code. Also, there won’t be a Speed
object in the output code.
// enum-const.js
// define a simple object
var racer = {
name: "Ross Geller",
speed: "medium" /* MEDIUM */,
};
// log `racer` object
console.log("racer =>", racer);
💡 TypeScript adds a JavaScript comment containing the name of the enum member where the enum member reference was substituted in the code. For example, you can see the
/* MEDIUM */
comment in the above code.
Enum Type and Enum Member Type
When we define an enum, TypeScript also defines it as a type with the same name (just like class definition). A variable (entity) annotated with this type must reference the member of this enum.
In the above example, the Racer
interface has the speed
property of the type Speed
which is an enum. Hence any value of the type Racer
must have speed
property and is value must be one of the members of the Speed
. In the above example, the value of the ross.speed
property is Speed.MEDIUM
.
Since enum members have constant values of string
or number
, therefore it is legal to provide a literal value for an entity of type enum. For example, ross.speed = 3
is legal since is equivalent to Speed.FAST
. Also, the reverse is also true as shown below.
let fastValue: number = Speed.FAST; // legal
Enum members themselves are also types on their own. You can apply the analogy of literal types to this. The enum type is a collective type while enum members also have their own unit types.
💡 The literal types concept is explained in the Type System lesson.
In the above example, the Racer
interface has the speed
property of the type Speed.MEDIUM
, which means the value of this property can only be Speed.MEDIUM
value and nothing else.
💡 The same rules apply for the constant enums.
Computed Enum Members
In most of the programming languages, the enum members must have compile-time constant values which mean all the values must be defined during compilation. But TypeScript allows expressions as values for the enum members which are computed at runtime.
TypeScript divides enum members based on their value initialization. If the value is available in the compilation phase, they are called constant members and when the value will be evaluated at runtime, they are called computed members. Let’s see a small example of this.
In the above example, Speed
enum has SLOW
and MEDIUM
members whose values can be predicated at compile-time, hence they are constant members. However, the parseInt
function is only available at runtime, hence the FAST
member is a computed member.
If an enum member doesn’t have a value initializer, then its previous member must be a numeric constant member so that the TypeScript compiler can assign an incremented value during the compile time.
💡 Constant enums (using
const
) can not have computed members since their values must be present during compilation phase.
TypeScript allows enum members to reference values from the same enum or another enum. You are also allowed to use +
, -
, ~
unary operators as well as +
, -
, *
, /
, %
, <<
, >>
, >>>
, &
, |
, ^
binary operators in the member value initialization expression which can only be used with constant values or constant enum members from the same or another enum.
In the example above, the MEDIUM
and FAST
members of the Speed
enum are computed using the members of the same enum. Using the OR
binary operator (|
), we can achieve some pretty useful enums.