loading...
Cover image for Flagged enum, why and how

Flagged enum, why and how

timdeschryver profile image Tim Deschryver Originally published at timdeschryver.dev ・3 min read

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,
}

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

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)
}

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

// 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
}

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

// output: 5

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)

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

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.

Posted on by:

timdeschryver profile

Tim Deschryver

@timdeschryver

NgRx team member - Writer for AngularInDepth

Discussion

markdown guide