Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / Typescript

Bringing the power of enum flags to TypeScript

5.00/5 (1 vote)
18 Jun 2014CPOL2 min read 28.1K  
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.

TypeScript
enum Example {
    Alpha, // 0
    Beta,  // 1
    Cappa, // 2
}

This simple definition produces this output.

JavaScript
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

TypeScript
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.  

TypeScript
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.

TypeScript
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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)