DEV Community

Cover image for Flagged enum, why and how
Tim Deschryver
Tim Deschryver

Posted on • Originally published at timdeschryver.dev

Flagged enum, why and how

Follow me on Twitter at @tim_deschryver | Subscribe to the Newsletter | Originally published on timdeschryver.dev.


Why

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:

// as enums

enum Days {
  Monday = 1,
  Tuesday = 2,
  Wednesday = 3,
  Thursday = 4,
  Friday = 5,
  Saturday = 6,
  Sunday = 7,
}

const selectedDays = [Days.Monday, Days.Wednesday] // [1, 3]

// as union types

type Days =
  | 'Monday'
  | 'Tuesday'
  | 'Wednesday'
  | 'Thursday'
  | 'Friday'
  | 'Saturday'
  | 'Sunday'

const selectedDays = ['Monday', 'Wednesday']

// as an array of booleans

const selectedDays = [true, false, true, false, false, false, false]

// as an object

const selectedDays = {
  monday: true,
  tuesday: false,
  wednesday: true,
  thursday: false,
  friday: false,
  saturday: false,
  sunday: false,
}
Enter fullscreen mode Exit fullscreen mode

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, // 1
  Tuesday = 1 << 1, // 2
  Wednesday = 1 << 2, // 4
  Thursday = 1 << 3, // 8
  Friday = 1 << 4, // 16
  Saturday = 1 << 5, // 32
  Sunday = 1 << 6, // 64
}

const selectedDays = Days.Monday | Days.Wednesday // 5
Enter fullscreen mode Exit fullscreen mode

How

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)
}
Enter fullscreen mode Exit fullscreen mode

This gives us the following array we can work with:

[1, 2, 4, 8, 16, 32, 64]
Enter fullscreen mode Exit fullscreen mode

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"]

// this is because the enum has as value
{
  "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
}
Enter fullscreen mode Exit fullscreen mode

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,
  )
}
Enter fullscreen mode Exit fullscreen mode

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,
])

// output: 5
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

This gives the following result:

const selectedDay = bitToFormValue(Days, 5)

/*
output: [
  true,   //  1 & 5
  false,  //  2 & 5
  true,   //  4 & 5
  false,  //  8 & 5
  false,  // 16 & 5
  false,  // 32 & 5
  false,  // 64 & 5
]
*/
Enter fullscreen mode Exit fullscreen mode

Angular form example

You can play around with an Angular reactive forms implementation:


Follow me on Twitter at @tim_deschryver | Subscribe to the Newsletter | Originally published on timdeschryver.dev.

Top comments (0)