AppDividend
Latest Code Tutorials

Understanding TypeScript enum: Const and Ambient Enum

0

Enum in TypeScript allows us to define a set of named constants. Enumerations or enums are a new data type supported by the TypeScript. Using enums make our life easier to document intent or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

For this tutorial, you must have installed TypeScript in your environment.

If you have not installed it, then you can install it using NPM.

npm install -g typescript

It will install it globally in your machine.

Now, you can compile using the following command.

tsc filename.ts

Types of Enums

There are three types of enums:

  1. Numeric enum
  2. String enum
  3. Heterogeneous enum

Numeric Enum

To define a numeric enum,  use the enum keyword.

// app.ts

enum score {
  batsman,
  bowler,
  fielder,
  Umpire,
}

Now, let’s transpile this file by the following command.

tsc app.ts

It will create the app.js file in the same directory.

The content of the app.js file is the following.

// app.js

var score;
(function (score) {
    score[score["batsman"] = 0] = "batsman";
    score[score["bowler"] = 1] = "bowler";
    score[score["fielder"] = 2] = "fielder";
    score[score["Umpire"] = 3] = "Umpire";
})(score || (score = {}));

The JavaScript transpiler will emit the lookup table, and the table looks like the above table.

When you have score.batsman in TypeScript, it emits score.batsman in JavaScript, which evaluates to 0.

The score[0] emits score[0] and evaluates to “batsman”. The reverse lookup options are unique to enums and can be pretty convenient.

Enums in TypeScript are always assigned numeric values when they are stored. The first value always takes a numeric value of 0, while the other property values in the enum are incremented by 1.

We also have the option to initialize the first numeric value ourselves. For example, we can write the same enum as the following.

enum score {
  batsman = 100,
  bowler,
  fielder,
  Umpire,
}

Now, transpile to JavaScript.

tsc app.ts

It will create an app.js file.

var score;
(function (score) {
  score[score["batsman"] = 100] = "batsman";
  score[score["bowler"] = 101] = "bowler";
  score[score["fielder"] = 102] = "fielder";
  score[score["umpire"] = 103] = "Umpire";
})(score || (score = {}));

In this example, we have initialized the batsman to 100, and from then, it will increment by 1 to the following properties.

That is why we get bowler = 101, fielder = 102, and umpire = 103.

Enum as a function parameter

The enum can be used as a function parameter. Let’s see how to pass enum as a function parameter.

// app.ts

enum data {
  x = 19,
  y = 21
}

function respond(recipient: string, message: data): void {
  console.log(recipient + message)
}

respond("The value of x is: ", data.x)

In this example, we have defined an enum called data.

Then we have defined a function called respond(), which takes two parameters.

  1. recipient – It is a type of string.
  2. message – It is a type of enum which we have recently defined.

Then in the body, we print both arguments and then call the respond() method.

Using the enum is simple: just access any member as the property off of an enum itself, and declare types using a name of the enum.

If you transpile to Javascript and run the JavaScript code, then you will see the following output.

node app
The value of x is: 19

Computed Enums in TypeScript

Numeric enums can include members with a computed numeric value.

The value of the enum member can be either a constant or computed.

The following enum includes members with computed values.

// app.ts

enum Millie {
  x = 19,
  y = 21,
  z = fetchValue('Eleven')
}

function fetchValue(val: String) {
  if (val == 'Eleven') {
    return 11
  }
  else {
    return 21
  }
}

console.log(Millie.z)

Transpile to Javascript code and run the file. You will see the following output.

11

But there is one caveat when we use computer enums.

Enums without initializers either required to be first or have to come after the numeric enums initialized with numeric constants or other constant enum members.

In other words, when the enum includes computed and constant members, the uninitiated enum members either must come first or must come after other initialized members with numeric constants.

If you do the following code, then you will get an error.

// app.ts

enum Millie {
  x = 19,
  z = fetchValue('Eleven'),
  y
}

function fetchValue(val: String) {
  if (val == 'Eleven') {
    return 11
  }
  else {
    return 21
  }
}

console.log(Millie.z)

In the above code, you can see that z has computed property, and next y is not initialized.

If you try to transpile the file, then you will get the following error.

app.ts:4:3 – error TS1061: Enum member must have initializer.

To avoid the above error, the above enum can be declared as below.

enum Millie {
  x = 19,
  y,
  z = fetchValue('Eleven'),
}

Now, it will not give any error.

String Enum

String enums are similar to numeric enums, except the enum values are initialized with the string values rather than numeric values.

The advantage of using string enum is that string enum offers better readability. If we have to debug code, it is easier to read string values rather than numeric values.

If you print the value of the variable, you won’t get a mysterious 0 or 1; you will get the actual string itself every time. Because of that, it’s also easily JSONifiable and produces the JSON that you can read in other languages without having to maintain the enum mapping in other languages.

// app.ts

enum StrangerThings {
  Character = "Eleven",
  Father = "Hopper",
  Power = "Telekenesis",
  Town = "Hawkins"
}

console.log(StrangerThings.Character)
console.log(StrangerThings.Power)

Output

Eleven
Telekenesis

In the above code, we have defined the string enum, StrangerThings, with the same values as the numeric enum above, but with a difference that these enum values are initialized with string literals.

The difference between numeric and string enum is that numeric enum values are auto-incremented, while string enum values need to be individually initialized.

Let’s say if you don’t initialize the Father property, then you will get an error like the following.

app.ts:3:3 – error TS1061: Enum member must have initializer.

For example, try to transpile the following code.

// app.ts

enum StrangerThings {
  Character = "Eleven",
  Father,
  Power = "Telekenesis",
  Town = "Hawkins"
}

console.log(StrangerThings.Character)
console.log(StrangerThings.Power)

In this example, the Father is not initialized. So, it will give us an error.

If you even initialized as an empty string, then you won’t get any error.

// app.ts

enum StrangerThings {
  Character = "Eleven",
  Father = "",
  Power = "Telekenesis",
  Town = "Hawkins"
}

console.log(StrangerThings.Character)
console.log(StrangerThings.Father)

Output

Eleven

It will print the empty string.

If you were debugging the code and had to read the runtime value of the numeric enum, the value is often opaque, it doesn’t convey any useful meaning on its own (though reverse mapping can often help), string enums enable us to give the meaningful and readable value when your code runs, independent from the name of an enum member itself.

Heterogeneous Enum

Heterogeneous enums contain both string and numeric values.

// app.ts

enum StrangerThings {
  Character = "Eleven",
  Father = "",
  Power = "Telekenesis",
  age = 15
}

console.log(StrangerThings.Character)
console.log(StrangerThings.age)

Output

➜  tsc app.ts
➜  node app
Eleven
15

Unless you’re trying to take advantage of JavaScript’s runtime behavior in a smart way, it’s advised that you don’t use a heterogeneous enum.

Reverse Mapping

TypeScript enum supports reverse mapping. What reverse mapping means we can access the value of a member and also a member name from its value.

// app.ts

enum StrangerThings {
  Character = 11,
  Father,
  Power,
  age = 15
}

console.log(StrangerThings.Power)
console.log(StrangerThings["Power"])
console.log(StrangerThings[13])

Output

➜  tsc app.ts
➜  node app
13
13
Power

From the above code, StrangerThings[13] returns its member name “Power”. This is because of reverse mapping.

Let’s learn how TypeScript implements reverse mapping using the following example.

// app.ts

enum StrangerThings {
  Character = 11,
  Father,
  Power,
  age = 15
}

console.log(StrangerThings)

Output

➜  tsc app.ts
➜  node app
{
  '11': 'Character',
  '12': 'Father',
  '13': 'Power',
  '15': 'age',
  Character: 11,
  Father: 12,
  Power: 13,
  age: 15
}

From the output, you can see that each value of the enum appears twice in the internally stored enum object. We know that enum values can be retrieved using a corresponding enum member value.

But it is also true that enum members can be retrieved using their values.

This is what we call a reverse mapping.

So, both the following mappings are true to enums: name -> value and value -> name.

const enum

In most cases, enums are a supremely valid solution. However, sometimes requirements are more airtight. To avoid paying the cost of extra generated code when accessing the enum values, it’s possible to use the const enums.

Const enums are defined using a const modifier on our enums.

We can write the following code to define a const enum.

const enum Dark { Mikkel, Jonas }

console.log(Dark.Mikkel)
console.log(Dark['Mikkel'])

console.log(Dark.Jonas)
console.log(Dark['Jonas'])

Output

➜ tsc app.ts
➜ node app
0
0
1
1

As usual, Enums are always assigned numeric values when they are stored.

The first value always takes a numeric value of 0, while the other values in the enum are incremented by 1.

But what about Dark[0]? This will error out at runtime, and your compiler should catch it. There is no lookup table, and the compiler doesn’t inline here.

// app.ts

const enum Data { Mikkel, Jonas }

console.log(Data[0])
console.log(Data[1])

Output

app.ts:3:18 - error TS2476: A const enum member can only be accessed using a string literal.

3 console.log(Dark[0])
                   ~

app.ts:4:18 - error TS2476: A const enum member can only be accessed using a string literal.

4 console.log(Dark[1])
                   ~

Found 2 errors.

Here, we got: A const enum member can only be accessed using a string literal.

In the above code, the –preserveConstEnumsflag will cause Dark to emit the lookup table. Its values will still be inlined, though.

Ambient enum

Ambient enums are used to describe the shape of already existing enum types.

declare enum Enum {
  A = 11,
  B,
  C = 21
}

A critical difference between ambient and non-ambient enums is that, in regular enums, members that don’t have the initializer will be considered constant if its preceding enum member is deemed to be constant.

In contrast, an ambient (and non-const) enum member that does not have an initializer is always considered computed.

Conclusion

Here are some reasons why enums are handy in TypeScript:

  1. With TypeScript enums you can create constants that you can easily relate to, making constants more readable.
  2. With TypeScript enum, developers have the liberty to develop memory-efficient custom constants in JavaScript. JavaScript does not support enums, but TypeScript helps us build and access them.
  3. TypeScript enum saves runtime and compile-time with inline code in JavaScript.
  4. TypeScript enum also supports certain flexibility that we previously had only in the programming languages like Java. This versatility makes it easy to express and document our aims and use cases efficiently.

Enums can be used inside array initializations just as other TypeScript data types.

Here are some cases where you don’t use ts enums.

  1. TypeScript enums cannot be used as variables; doing so would return errors.
  2. When you plan to reassign or modify the enum member values, enums are type-safe and therefore, would return compile errors on reassignment.
  3. When you need to record dynamic values, enums are best suited for finite elements, and the general idea behind was to help build the user-defined constants system.

That is for the TypeScript enum tutorial.

See also

Getting started with TypeScript

Setup TypeScript with Webpack

Use TypeScript with Vue

Leave A Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.