Derek Lawless

There is always sunshine / Far above the grey sky

Enumerations are user-defined types consisting of named values (members) that behave as constants. They’re useful when you want to define a type consisting of a restricted set of values, for example the colours of the rainbow.

Numeric enums

By default, numeric enums start at zero and increment by 1 for each subsequent member:

enum Volume {
	Low,
	Medium,
	High
}

console.log(Volume.Low);    // 0
console.log(Volume.Medium); // 1
console.log(Volume.High);   // 2

You can change this if you wish and either start with a non-zero value or explictly assign values to members:

enum StoryPoints {
	Zero = 0,
	One = 1,
	Two = 2,
	Three = 3,
	Five = 5,
	Eight = 8,
	Thirteen = 13
}

console.log(StoryPoints.Eight); // 8

Numeric enums can be reverse mapped, in other words you can use the numeric value to retrieve the associated member:

console.log(StoryPoints[3]); // Three

String enums

TypeScript version 2.4 introduced support for string initialisers in enums. This can be useful for defining a restricted set of string values:

enum MimeTypes {
	CSV = "text/csv",
	HTML = "text/html",
	JSON = "application/json",
	XML = "application/xml"
}

console.log(MimeTypes.JSON); // application/json

As the release notes state, you cannot reverse map a string-based enum:

console.log(MimeTypes["application/xml"]); // Error!

Bitwise enums

Bitwise enums (also known as Flag enums) are useful when you want flexbility in combining enum members to represent new values. One such use might be where you want to elevate or remove file access permissions:

enum FileAccess {
	None = 0,
	Read = 1 << 0,  // Can also be expressed as Read = 1
	Write = 1 << 1, // Can also be expressed as Write = 2
	Delete = 1 << 2 // Can also be expressed as Delete = 4
}

While the FileAccess enum could be defined with explicit members for every combination, as that number increases the approach quickly becomes impractical.

As you may have noticed from the above example, for bitwise enums use values that are powers of 2 (2^0, 2^1, 2^2, 2^3, and so on).

And in action:

let access = FileAccess.None;
console.log(access); // 0

// Elevate to Read access
access = FileAccess.Read;
console.log(access); // 1

// Elevate to Read and Write access
access |= FileAccess.Write;
console.log(access); // 3

// Elevate to Read, Write, and Delete access
access |= FileAccess.Delete;
console.log(access); // 7

// Remove Delete access
access &= ~FileAccess.Delete;
console.log(access); // 3

// Evaluating incorrectly
console.log(access === FileAccess.Read); // false

// Evaluating correctly
console.log(FileAccess.Read === (access & FileAccess.Read)); // true

Note the |= and &= ~ operators. The |= operator allows to perserve any existing assigned value(s) - using = alone will overwrite. The &= ~ operator allows you to remove one or more assigned values.


const enums

If you wish to avoid the overhead of the generated code and additional indirection, you can use a const enum:

const enum Rainbow { // Note the const modifier
	...
}

With a const enum, the values are inlined with each use. Note that only constant enum expressions can be used, you cannot use computed values as in this contrived example:

const enum StoryPoints {
	Zero = 0,
	One = 1,
	Two = 2,
	Three = One | Two,
	Five = ".....".length() // Error!
	Eight = 8,
	Thirteen = 13
}
© 2022 Derek Lawless. Built with Gatsby