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.