Home

Typescript Enums: Supersets, subsets, and equal enums

Typescript Enums offer a lot of functionality out of the box, but sometimes we want to arrange them in ways to create subsets or supersets of another enum.

Most often you don’t need to over engineer this.

While the bellow solutions don’t use the enum keyword, they are very simple and will serve most use cases.

Two equal enums

Use case: Adding values to AccountStateEnum will also add to UserStateEnum.

This helps keeping things DRY when two enums are equal.

Implementation:

enum AccountStateEnum {
  APPROVED = 'Approved',
  REJECTED = 'Rejected'
}

type UserStateEnum = AccountStateEnum;
const UserStateEnum = { ...AccountStateEnum };

UserStateEnum behaves like AccountStateEnum

Example Usage:

let accountState: AccountStateEnum;
accountState = AccountStateEnum.REJECTED; // no error
accountState = UserStateEnum.APPROVED; // no error

Enums are interchangeable

Enum Supersets

Use case: When enum B must contain all values from enum A and some additional values.

You can also create enums that extend other TypeScript enums by using const assertions like so:

Implementation:

const GameResultEnum = {
  WINNER: 'Winner',
  LOSER: 'Loser'
} as const;
type GameResultEnum = typeof GameResultEnum[keyof typeof GameResultEnum];

const SoccerGameResultEnum = { ...GameResultEnum, DRAW: 'Draw' } as const;
type SoccerGameResultEnum = typeof SoccerGameResultEnum[keyof typeof SoccerGameResultEnum];

SoccerGame can also DRAW

Usage:

let gameResult: GameResultEnum, soccerGameResult: SoccerGameResultEnum;

soccerGameResult = SoccerGameResultEnum.WINNER; // no errors
soccerGameResult = SoccerGameResultEnum.DRAW; // no errors
soccerGameResult = GameResultEnum.LOSER; // GameResultEnum can also be used

gameResult = GameResultEnum.WINNER; // no errors
gameResult = SoccerGameResultEnum.WINNER; // SoccerGameResultEnum can also be used
gameResult = SoccerGameResultEnum.DRAW; // correctly throws errors

Adding to new values to GameResultEnum will also add to SoccerGameResultEnum.

Enum Subsets

Use case: You have a large base enum A with many values. Create enum B which equals A, except for removing only a few selected values.

Using the above SoccerGameResultEnum, let’s create a RiggedGameResultEnum where the result can only be a win.

Implementation:

// Remove LOSER and DRAW. Assign the rest to RiggedGame
const { LOSER, DRAW, ...RiggedGameResultEnum } = SoccerGameResultEnum;
type excludedOptions =
  | typeof SoccerGameResultEnum.LOSER
  | typeof SoccerGameResultEnum.DRAW;
type RiggedGameResultEnum = Exclude<SoccerGameResultEnum, excludedOptions>;

RiggedGameResultEnum contains only SoccerGameResultEnum.WINNER.

Usage:

let riggedGameResult: RiggedGameResultEnum;

riggedGameResult = SoccerGameResultEnum.LOSER; // errors
riggedGameResult = SoccerGameResultEnum.DRAW; // errors
riggedGameResult = SoccerGameResultEnum.WINNER; // Ah, we knew it! No errors

Everything else results in errors.

Conclusion

Enums are great. They help your code base by allowing you to create cohesive groups of constants that semantically make sense together.

Typescript is evolving very fast. As of version 3.6 the above is the simplest way I found that keeps enum relations DRY.

This will improve your code maintainability, auto-completion, and will prevent changes that would break your enum relations.

If you want to see the generated JavaScript, here is the TypeScript Playground.