Bringing the power of enum flags to TypeScript





5.00/5 (1 vote)
An implementation of flag enumerations for Typescript
Introduction
I love TypeScript. It brings the surity of static typing to the dynamo of the JavaScript language in a way that helps to support large complex projects. One of the best things it has is the enumeration. We all know the enumeration and the benefits of using an enumeration are well known. However one thing that TypeScript does not support out of the box is the concept of a flagged enumeration. This is useful in many circumstances and a desired tool in the TypeScript belt. The following examples and code is an implementation based on how C# handles Flagged enumerations.
Background
TypeScript defines and enumeration simply as you would expect.
enum Example {
Alpha, // 0
Beta, // 1
Cappa, // 2
}
This simple definition produces this output.
var Example;
(function (Example) {
Example[Example["Alpha"] = 0] = "Alpha";
Example[Example["Beta"] = 1] = "Beta";
Example[Example["Cappa"] = 2] = "Cappa";
})(Example || (Example = {}));
This construct allows you to determine the enum value quickly and easily and is useful in switch statements and code readability. However what this does not do is easily allow you to turn this into a flagged enumeration with bitwise operations and comparison.
With the use of a bitwise shift operator we can turn this simple enum into a flagged enum
enum Example {
Alpha = 1 << 0, // 1
Beta = 1 << 1, // 2
Cappa = 1 << 2, // 4
}
However this is only the start of what we need to get full functionality and usability out of the enumeration. It does not support intersects, to strings, contains, add, subtract or equal operators easily or smoothly.
Turning this into a comprehensive enumeration takes a little extra work.
The Code
The following code nugget enables you to turn an enum into a flagged enum class with a single line of code.
export module FlaggedEnum {
"use strict";
export interface IFlaggedEnumGenerator {
(_enum: any, _max: number): IFlaggedEnum;
}
export interface IFlaggedEnum {
(val: IFlaggedEnum): void;
(val: number): void;
(val: string): void;
/** array of the individual enum flags that represent the value
*/
toArray(): IFlaggedEnum[];
/** does this instance contain all the flags of the value
*/
contains(val: IFlaggedEnum): boolean;
contains(val: number): boolean;
contains(val: string): boolean;
/** adds the flags to the value and returns a new instance
*/
add(val: IFlaggedEnum): IFlaggedEnum;
add(val: number): IFlaggedEnum;
add(val: string): IFlaggedEnum;
/** removes the flags from the value and returns a new instance
*/
remove(val: IFlaggedEnum): IFlaggedEnum;
remove(val: number): IFlaggedEnum;
remove(val: string): IFlaggedEnum;
/** returns an instance containing all intersecting flags
*/
intersect(val: IFlaggedEnum): IFlaggedEnum;
intersect(val: number): IFlaggedEnum;
intersect(val: string): IFlaggedEnum;
/** does the two instances equal each other
*/
equals(val: IFlaggedEnum): boolean;
equals(val: number): boolean;
equals(val: string): boolean;
}
/** create a class definition for a Flagged Enum
* @method create
* @param _enum {enum} The enum definition being exteded
* @param _max {number} the maximum possible value of the enum being extended
* @returns {IFlaggedEnum} the class definition for the provided enum
*/
export var create: IFlaggedEnumGenerator = function (_enum: any, _max: number): IFlaggedEnum {
var base: any = _enum,
max: number = _max;
var Base: IFlaggedEnum = <any>function (val: any): void {
if (typeof (val) === "string") {
val = base[val];
}
this.value = val + 0;
};
var proto: any = Base.prototype;
proto.valueOf = function (): number { return <number>this.value; };
proto.toString = function (): string {
var list: string[] = [];
for (var i: number = 1; i < max; i = i << 1) {
if ((this.value & i) !== 0) {
list.push(base[i]);
}
}
return list.toString();
};
proto.toArray = function (): IFlaggedEnum[] {
var list: IFlaggedEnum[] = [];
for (var i: number = 1; i < max; i = i << 1) {
if ((this.value & i) !== 0) {
list.push(new Base(i));
}
}
return list;
};
proto.contains = function (val: any): boolean {
if (typeof (val) === "string") {
val = base[val];
}
return (this.value & val) === (val + 0);
};
proto.add = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = base[val];
}
return new Base(this.value | val);
};
proto.remove = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = this.base[val];
}
return new Base((this.value ^ val) & this.value);
};
proto.intersect = function (val: any): IFlaggedEnum {
if (typeof (val) === "string") {
val = base[val];
}
var final: number = 0;
for (var i: number = 1; i < max; i = (i << 1)) {
if ((this.value & i) !== 0 && (val & i) !== 0) {
final += i;
}
}
return new Base(final);
};
proto.equals = function (val: any): boolean {
if (typeof (val) === "string") {
val = base[val];
}
return this.value === (val + 0);
};
return Base;
};
}
Using the code
Using this module is simple. It is designed to create a class that that you can interact with and manipulate in place of your enum. So starting with our enum Example, we would create a flagged enum Example class like this.
var FlaggedExample: IFlaggedEnum = FlaggedEnum.create(Example, 1 << 2); // class definition
var example = new FlaggedExample(3); // Alpha,Beta instance of the class
Now we have a class that we can instantiate and variable that we can manipulate. The class definition is only needed one time for every enum type you want to convert into a Flagged Enum. Instances of the class can accept strings, enum and number values interchangeably in all their operations.
example.add(4); // [7] Alpha, Beta, Cappa example.remove("Alpha"); // [6] Beta, Cappa example.contains(Example.Alpha); // false example.contains("Beta"); // true example.equals(6); // true example.intersect(new FlaggedExample(5)); // {FlaggedExample} [4] Cappa example.toArray(); // [{FlaggedExample} [2] Beta, {FlaggedExample} 4 Cappa] example.valueOf(); // 6 example.toString(); // "Beta,Cappa" example + 0; // 6 example + Example.Alpha; // 7
And thats it for this walk through. Go forth and flag.
History
The repository for this code is maintained @ Bitbucket.org