Home > Back-end >  Define enum as static property of class
Define enum as static property of class

Time:05-27

I want to be able to define a class with a static property on it that contains an enum. I've got the JavaScript working here, I'm just trying to iron out the types. Simplified example below.

box.ts:

enum Color {
  RED,
  GREEN,
  BLUE,
}

export class Box {
  static Color = Color;

  private color: Color;

  constructor(color: Color) {
      this.color = color;
  }
}

main.ts:

import {Box} from './box';

function createBox(color: Box.Color): Box {
// ERROR: Box.Color isn't a type ^
  return new Box(color);
}

const box = createBox(Box.Color.RED);

As seen above, I want to be able to reference the Color type without importing it directly. I want to reference it as a member of Box, either Box.Color or Box['Color'] or similar. The JavaScript Box.Color.RED works fine as a value, but how do I refer to the type relative to Box? Is there something I can add to Box's definition to also export the Color type along with it?

I do not want to export enum Color and then import {Color}. I want to refer to it as a Box Color somehow and be able to just pass around the one Box type to contain the information.


I have found this solution, but it is far too verbose:

function createBox(color: (typeof Box.Color)[keyof typeof Box.Color]): Box {
  return new Box(color);
}

or, similarly, but it is too much of a hack:

enum Color {RED, GREEN, BLUE}

class Box {
  static Color = Color;
  // Defining a value that I don't use works along with `typeof`
  // to get the type, but seems like a poor hack and makes
  // for a bad API.
  static color: Color;
  constructor(color: Color) {}
}

function createBox(color: typeof Box.color): Box {
  return new Box(color);
}

Is there anyway to modify the Box class to something like the following or anything of the sort?

export class Box {
  static Color = Color;
  // I want the `Color` type to export as part of the `Box` type.
  static type Color: Color;

  private color: Color;

  constructor(color: Color) {
      this.color = color;
  }
}

CodePudding user response:

See Merging Namespaces with Classes in the TypeScript Handbook (I've inlined the full documentation section at the end of this answer so that if the link changes, the content will persist here). An example tailored to the data in your question:

TS Playground

export class Box {
  constructor(private color: Box.Color) {}
}

export namespace Box {
  export enum Color {
    RED,
    GREEN,
    BLUE,
  }
}

function createBox (color: Box.Color): Box {
  return new Box(color);
}

const box = createBox(Box.Color.RED);
console.log(box); // { color: 0 }


From the handbook:

Merging Namespaces with Classes

This gives the user a way of describing inner classes.

class Album {
  label: Album.AlbumLabel;
}
namespace Album {
  export class AlbumLabel {}
}

The visibility rules for merged members is the same as described in the Merging Namespaces section, so we must export the AlbumLabel class for the merged class to see it. The end result is a class managed inside of another class. You can also use namespaces to add more static members to an existing class.

In addition to the pattern of inner classes, you may also be familiar with the JavaScript practice of creating a function and then extending the function further by adding properties onto the function. TypeScript uses declaration merging to build up definitions like this in a type-safe way.

function buildLabel(name: string): string {
  return buildLabel.prefix   name   buildLabel.suffix;
}

namespace buildLabel {
  export let suffix = "";
  export let prefix = "Hello, ";
}

console.log(buildLabel("Sam Smith"));

Similarly, namespaces can be used to extend enums with static members:

enum Color {
  red = 1,
  green = 2,
  blue = 4,
}

namespace Color {
  export function mixColor(colorName: string) {
    if (colorName == "yellow") {
      return Color.red   Color.green;
    } else if (colorName == "white") {
      return Color.red   Color.green   Color.blue;
    } else if (colorName == "magenta") {
      return Color.red   Color.blue;
    } else if (colorName == "cyan") {
      return Color.green   Color.blue;
    }
  }
}
  • Related