ts-enums: Bringing Java-style enums to TypeScript

I’ve been working as a developer primarily in TypeScript for a couple years, and I really love the language. When I transitioned from Java to JavaScript in 2014, I loved the freedom and functional programming that JavaScript provided, but I missed the classes and types from Java. TypeScript has been a great intermediate language for me, giving me access to the web and functional programming, but with an optional structure that feels at home.

One area that was missing for me in TypeScript, though, was Enums. TypeScript has an Enum concept, but it’s nowhere nearly as powerful as Java’s Enums. Whereas TypeScript Enums are essentially namespaced groups of numbers (or strings, as of 2.4), Enums in Java are full classes. Not only can you create Java Enums that simply create a collection of namespaced strings, but you can also implement properties and logic for all of the instances (which I used to full effect in an article I wrote for OCI on extending them).

Fortunately, Dr. Axel Rauschmeyer created Enumify to bring an analogue of Java’s Enums to JavaScript (in a blog post, he expands on why a naive implementation of enums in JavaScript lacks the power of Java Enums).

Enumify is a strong introduction to class-style enums in JavaScript, and I’m happy to announce that I’ve forked Enumify to release ts-enums, a TypeScript-specific extension of Dr. Rauschmeyer’s ideas, with a slightly different API.

An Example

import {Enum, EnumValue} from 'ts-enums';

export class Color extends EnumValue {
  constructor(name: string) {
    super(name);
  }
}

class ColorEnumType extends Enum {

  RED: Color = new Color('RED name');
  GREEN: Color = new Color('GREEN name');
  BLUE: Color = new Color('BLUE name');
  BLACK: Color = new Color('BLACK name');

  constructor() {
    super();
    this.initEnum('Color');
  }
}

export const ColorEnum: ColorEnumType = new ColorEnumType();

// examples of use
console.log(ColorEnum.RED.toString()); // Color.RED
console.log(ColorEnum.GREEN instanceof Color); // true

Properties of Enum classes

Enum exposes the getter values, which produces an Array with all enum values:

for (const c of ColorEnum.values) {
  console.log(c);
}
// Output:
// Color.RED
// Color.GREEN
// Color.BLUE

Enum exposes the methods byPropName and byDescription, to extract the EnumValue instance by either the property name of the object in the Enum or the description string passed into the EnumValue’s constructor, respectively:

console.log(ColorEnum.byPropName('RED') === ColorEnum.RED); // true
console.log(ColorEnum.byDescription('RED name') === ColorEnum.RED); // true

Enums as classes

The EnumValues are full TypeScript classes, enabling you to add properties and methods (see the tests for more examples).

import {Enum, EnumValue} from 'ts-enums';
import {Color, ColorEnum} from 'color';

class BridgeSuit extends EnumValue {
  constructor(name: string, private _isMajor: boolean = false) {
    super(name);
  }

  // can use simple properties (defensively-copied 
  // to maintain immutability)
  get isMajor(): boolean{
    return this._isMajor;
  }

  // can also use methods, though this isn't an ideal implementation. 
  // (I would probably used another property instead of an if-else)
  get color(): Color {
    if (this === BridgeSuiteEnum.SPADES 
        || this === BridgeSuiteEnum.CLUBS) {
      return ColorEnum.BLACK;
    } else {
      return ColorEnum.RED;
    }
  }
}

class BridgeSuitEnumType extends Enum {
  SPADES: BridgeSuit = new BridgeSuit('Spades', true);
  HEARTS: BridgeSuit = new BridgeSuit('Hearts', true);
  DIAMONDS: BridgeSuit = new BridgeSuit('Diamonds');
  CLUBS: BridgeSuit = new BridgeSuit('Clubs');

  constructor() {
    super();
    this.initEnum('BridgeSuit');
  }

  // can also have methods on the overall type
  get majors(): BridgeSuit[] {
    return this.values.filter((suit: BridgeSuit ) => suit.isMajor);
  }
}

const BridgeSuitEnum: BridgeSuitEnumType = 
  new BridgeSuitEnumType();

console.log(BridgeSuitEnum.HEARTS.color.toString()); // Color.RED
console.log(BridgeSuitEnum.HEARTS.isMajor); // true

More information

See ngrx-example-app-enums for a more complicated implementation supporting an @ngrx app.

Advertisements

About Lance Finney

Father of two boys, Java developer, Ethical Humanist, and world traveler (when I can sneak it in). Contributor to Grounded Parents.
This entry was posted in Programming and tagged , , , . Bookmark the permalink.

One Response to ts-enums: Bringing Java-style enums to TypeScript

  1. Pingback: Using enums to reduce boilerplate in ngrx | Lance's Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s