- Published on
TypeScript: Using Enums, a complete guide
Enums are an essential feature in TypeScript, allowing you to define a set of named constants that represent a finite set of values. Enums can be used in various scenarios, such as defining a set of options for a dropdown or radio button selection or representing different types of data.
In this guide, we will explore how to use Enums in TypeScript with a practical example. We will define an interface for a member with a subscription and use Enums to represent different types of subscriptions.
Table of Contents
Defining an Interface with Enums
Let's start by defining an interface for a member with a subscription. We will use an interface called Member
to represent a member's details, including their full name, subscription ID, and subscription type.
Example:
interface Member {
fullName: string;
subscriptionID?: number;
subscriptionType :SubscriptionType
}
In this interface, we have three properties:
fullName
: a required string property that represents the member's full name. subscriptionID
: an optional number property that represents the member's subscription ID. subscriptionType
: a required property that represents the member's subscription type. Now, let's define an Enum for subscription types.
enum SubscriptionType {
Classic,
Premium,
Gold,
}
In this scenario, the Enum called SubscriptionType
has three members: Classic, Premium, and Gold. These members are assigned numeric values by default, starting from 0 for the first member and incrementing by 1 for each subsequent member. However, you can assign custom values to members as well.
Using Enums as Attributes
Now that we have defined our interface and Enums, let's see how to use them. We can create a new Member object with the following code:
const member: Member = {
fullName: 'John Smith',
subscriptionType: SubscriptionType.Premium,
}
Above, the new Member
object with the full name 'John Smith' and a subscriptionType of SubscriptionType.Premium
is created. Since we did not specify a subscriptionID, it will be undefined
.
We can also use Enums to switch between different subscription types and perform different actions based on the selected subscription type.
Example:
const getSubscriptionCost = (subscriptionType: SubscriptionType): number => {
switch (subscriptionType) {
case SubscriptionType.Classic:
return 10
case SubscriptionType.Premium:
return 20
case SubscriptionType.Gold:
return 30
default:
throw new Error('Invalid subscription type.')
}
}
const subscriptionType = SubscriptionType.Gold
const subscriptionCost = getSubscriptionCost(subscriptionType)
console.log(`Subscription cost: ${subscriptionCost}`)
The arrow function called getSubscriptionCost
takes a subscriptionType
parameter of type SubscriptionType
and returns the corresponding subscription cost. We use a switch statement to check the subscriptionType parameter and return the appropriate cost.
A variable called subscriptionType
holds the value of SubscriptionType.Gold
. This value is been passed to the getSubscriptionCost()
arrow function. The result 30 is filtered by the switch statement.
Converting Enum Keys or Values into an Array
There are several scenarios where you might want to convert an Enum's keys or values into an array:
Displaying Enum values in a dropdown or a list: You might want to display all the available options of an Enum in a dropdown or a list, which requires converting Enum keys or values into an array.
Looping over Enum values: If you need to perform some operations or validations on each Enum value, you might want to loop over the Enum values by converting them into an array.
Converting Enum keys or values into a string: If you need to serialize an Enum or pass its values as a string to some API, you might want to convert Enum keys or values into a comma-separated string.
Here is an example of how to convert an Enum's values :
enum SubscriptionType {
Classic,
Premium,
Gold,
}
//Values
const subTypesValuesArray = Object.values(SubscriptionType)
console.log(subTypesValuesArray) // Returns: ["Classic", "Premium", "Gold", 0, 1, 2]
//Keys
const subTypesKeysArray = Object.keys(SubscriptionType)
console.log(subTypesKeysArray) // Returns: ["0", "1", "2", "Classic", "Premium", "Gold"]
//Get Keys as Array: Method A
const subscriptionTypesArray = Object.keys(SubscriptionType)
.filter((k) => typeof SubscriptionType[k as any] === 'number')
console.log(subscriptionTypesArray) // Returns: ["Classic", "Premium", "Gold"]
//Get Keys as Array: Method B
const subscriptionTypesArray2 = Object.keys(SubscriptionType)
.filter((value) => isNaN(Number(value)) === false)
.map((key) => SubscriptionType[key])
console.log(subscriptionTypesArray2) // Returns: ["Classic", "Premium", "Gold"]
Evaluating Enum values of instantiated objects
By reusing above Method B
we can create a getValidSubsTypes
function that can return all enum keys in an Array. From there if we will need to validate an existing value agains that array we can use memberHasValidSubscription
to confirm if it matches with any Enum value. Here there's an example how to validate
interface Member {
fullName: string
subscriptionID?: number
subscriptionType: SubscriptionType // ❌ just to allow a falsy entry for the example purpose
//subscriptionType: SubscriptionType.Classic | SubscriptionType.Premium | SubscriptionType.Gold //✅ prefered solution
}
enum SubscriptionType {
Classic,
Premium,
Gold,
}
const member: Member = {
fullName: 'Johnny Smith',
subscriptionType: SubscriptionType.Gold,
}
const memberFromUI: Member = {
fullName: 'Mike Smith',
subscriptionType: 5,
}
const isValidSubscriptionType = (subscriptionType: SubscriptionType) => {
return Object.values(SubscriptionType).includes(subscriptionType)
}
const memberHasValidSubscription = (member: Member): boolean => {
if (!isValidSubscriptionType(member.subscriptionType)) {
throw new Error(`${member.fullName} has Invalid subscription type`)
}
return true
}
try {
console.log(memberHasValidSubscription(member)) //Returns true
console.log(memberHasValidSubscription(memberFromUI)) //Throws Error 🚫
} catch (e) {
console.error('%s', e.message) //Mike Smith has Invalid subscription type
}
Enums are a powerful feature in TypeScript that allow you to define a set of named constants. In this guide, we have explored how to define an interface with an Enum property and use Enums to represent different types of subscriptions. We have also seen how to switch between different subscription types and perform different actions based on the selected subscription type.
With these examples, you should now be able to start using Enums in your TypeScript projects and leverage their power to make your code more readable and maintainable.
EXTRA: Alternative Best Practices
When it comes to defining a set of related values in TypeScript, many developers instinctively reach for the enum keyword. Enums are a convenient way to give names to values, but they have several limitations that can make them less than ideal for certain use cases. One alternative that is worth considering is the use of union types.
Union types allow you to define a type that can represent one of several different possible values, making them more flexible and easier to work with than enums in many situations.
Consider the example of defining the Member
interface that includes the subscriptionType
field. Using an enum to define the possible subscription types might seem like a natural choice, but this approach has some drawbacks
For one thing, enums are limited to a fixed set of values that are defined at compile time. If you need to add a new subscription type later, you will have to modify and recompile your code. Additionally, enums can be verbose and repetitive to work with, as you have to use the enum name to refer to each value.
A better approach in this case would be to define subscriptionType
as a union type, using string literals to represent the possible values.
Here's an example of how this could look:
interface Member {
fullName: string;
subscriptionID?: number;
subscriptionType: 'Classic' | 'Premium' | 'Gold'
}
With this approach, you will get all the benefits of enums (named values, type safety, etc.) without their limitations. You can easily add new subscriptionType
simply by adding a new string literal to the union and you don't have to worry about runtime issues like Enum value collisions.
Overall, while enums have their uses, it's worth considering the benefits of using union types in situations where you need more flexibility and less boilerplate.