Click here to Skip to main content
Click here to Skip to main content

Bringing the power of enum flags to TypeScript

, 18 Jun 2014
Rate this:
Please Sign up or sign in to 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

License

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

Share

About the Author

Daniel Gidman
Architect Stackify
United States United States
No Biography provided
Follow on   Twitter   Google+

Comments and Discussions

 
-- There are no messages in this forum --
| Advertise | Privacy | Mobile
Web01 | 2.8.140821.2 | Last Updated 18 Jun 2014
Article Copyright 2014 by Daniel Gidman
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid