TypeScript Enums for Defining Named Constants
TypeScript enums provide a powerful way to define a set of named constants, making your code more maintainable, type-safe, and self-documenting.
Think of them as a way to group related values—like roles, states, or options—under one neat umbrella. Instead of scattering random strings or numbers throughout your code, enums let you define a set of constants with meaningful names.
Here's a simple example:
enum UserRole {
Admin = "ADMIN",
Editor = "EDITOR",
Viewer = "VIEWER",
}
This enum defines user permission levels in an application. Instead of using raw strings like "ADMIN" or "EDITOR" throughout the code, it allows you to use UserRole.Admin
or UserRole.Editor
. This prevents typos, enables autocomplete, and makes it easier to track and update role names across your codebase. We'll revisit this example in use cases.
In this article we'll cover:
- Ambient vs non-ambient enums
- Enum mapping (bidirectional and unidirectional)
- Constant and computed enum member values
- How enums translate to JS
- Heterogeneous enums
- Numeric enums
- Enum use cases
Ambient Enums in TypeScript
Ambient enums are a special type of enum used when the enum values are defined externally, often in a separate library or environment, and need to be declared in TypeScript to enable type-checking.
Ambient vs. Non-Ambient Enums
- Ambient Enums: Declared with the
declare
keyword and exist only at compile time. They don't generate any JavaScript output. - Non-Ambient Enums: Declared normally and are fully compiled into JavaScript.
Here's an example:
// Ambient Enum
declare enum ExternalEnum {
Start = 1,
Stop = 2,
}
// Non-Ambient Enum
enum InternalEnum {
Start = 1,
Stop = 2,
}
Limitations of Ambient Const Enums
Ambient enums cannot include computed or non-literal values:
declare const enum ExternalEnum {
Value = Math.random(), // Error: Ambient const enums can only have literal values
}
Enum Mapping in TypeScript
TypeScript supports both unidirectional and bidirectional mappings for enums, which allow you to look up values either by name or by value.
Unidirectional Mapping
In a string enum, you can map from the key to its value:
enum StringEnum {
Pending = "PENDING",
Approved = "APPROVED",
}
console.log(StringEnum.Pending); // Output: "PENDING"
Bidirectional Mapping
For numeric enums, TypeScript automatically creates both forward and reverse mappings:
enum NumericEnum {
Start = 1,
Stop,
}
console.log(NumericEnum.Start); // Output: 1
console.log(NumericEnum[1]); // Output: "Start"
This bidirectional behavior is unique to numeric enums and doesn't apply to string enums.
Constant and Computed Enum Member Values
Enum members can be either constant or computed. Constant members are known at compile-time, while computed members are evaluated at runtime.
Constant Members
Constant members can be explicitly or implicitly initialized:
enum Constants {
A = 1, // Explicit
B, // Implicit (auto-increment from A)
C = 10, // Explicit
}
console.log(Constants.B); // Output: 2
Computed Members
Computed members require runtime evaluation:
enum Computed {
A = 10,
B = A * 2, // Computed
}
console.log(Computed.B); // Output: 20
String Enum Initialization
String enum members must always be explicitly initialized:
enum StringEnum {
A = "Alpha",
B = "Beta",
}
How Enums Translate to JavaScript
At runtime, enums translate into plain JavaScript objects. Here's what happens: