Flagged enum, why and how

February 24, 2020

The TypeScript docs define enums as follows:

Enums allow us to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.

An enum can be stored as a single value, but storing a collection of enum values is verbose. Especially if you're using a relational database, for example SQL Server, where you need to create a different table to store these values.

Let's use a selection of weekdays as an example, where a user can select one or more days. In code, we have different structures to store a user's selection:

enum Days { Monday = 1 , Tuesday = 2 , Wednesday = 3 , Thursday = 4 , Friday = 5 , Saturday = 6 , Sunday = 7 , } const selectedDays = [ Days . Monday , Days . Wednesday ] type Days = | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday' | 'Sunday' const selectedDays = [ 'Monday' , 'Wednesday' ] const selectedDays = [ true , false , true , false , false , false , false ] const selectedDays = { monday : true , tuesday : false , wednesday : true , thursday : false , friday : false , saturday : false , sunday : false , }

While these structures work, they aren't optimal when you need to send them to a backend service. To make it easier for us, we can use flagged enums. A flagged enum can be used to efficiently send and store a collection of boolean values.

In a flagged enum, each value of the enum is assigned to a bit value. These must be bit values because each combination possible will be unique. That's why flagged enums are useful, they provide a way to efficiently work with a collection of values.

enum Days { Monday = 1 << 0 , Tuesday = 1 << 1 , Wednesday = 1 << 2 , Thursday = 1 << 3 , Friday = 1 << 4 , Saturday = 1 << 5 , Sunday = 1 << 6 , } const selectedDays = Days . Monday | Days . Wednesday

To work with these values, we make use of bitwise operators.

The first step is to convert the Days enum to an array of bit numbers.

function enumToBitValues ( enumValue : object ) { return Object . keys ( enumValue ) . map ( Number ) . filter ( Boolean ) }

This gives us the following array we can work with:

[ 1 , 2 , 4 , 8 , 16 , 32 , 64 ]

It's important to filter out non-numbers values, otherwise the output will look as follows:

[ "1" , "2" , "4" , "8" , "16" , "32" , "64" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" , "Sunday" ] { "1" : "Monday" , "2" : "Tuesday" , "4" : "Wednesday" , "8" : "Thursday" , "16" : "Friday" , "32" : "Saturday" , "64" : "Sunday" , "Monday" : 1 , "Tuesday" : 2 , "Wednesday" : 4 , "Thursday" : 8 , "Friday" : 16 , "Saturday" : 32 , "Sunday" : 64 }

A flagged enum is stored as a single value, but our front-end is represented as a checkbox list. To map the user's selection to a single value, we create a sum of the selected values:

function formValueToBit ( enumeration : object , selection : boolean [ ] ) { const bits = enumToBitValues ( enumeration ) return selection . reduce ( ( total , selected , i ) => total + ( selected ? bits [ i ] : 0 ) , 0 , ) }

If we select monday and wednesday this formValueToBit function will have 5 as output:

const selectedDay = formValueToBit ( Days , [ true , false , true , false , false , false , false , ] )

To do the inverse and map the value back to an array of booleans, to determine if a checkbox must be checked or not, we use the bitwise AND operator.

function bitToFormValue ( enumeration : object , bit : number ) { const bits = enumToBitValues ( enumeration ) return bits . map ( ( b ) => ( bit & b ) === b ) }

This gives the following result:

const selectedDay = bitToFormValue ( Days , 5 )

You can play around with an Angular reactive forms implementation:

Send a message