Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I create nested enums in Typescript

Java programmer here. I am setting up some kind of nested enum construction. Below is a short annotated example of what I am working on. I know how to solve this in Java, but I cannot figure out how to do this in Typescript.

Basically I have a list of EntityNames, each entityName has a bunch of entityTypeNames. I want to create functions/methods that accept the entityTypeName as an argument and can do some logic that also can find the entityName that belongs to this EntityTypeName. It's important to easily pass arguments to these functions/methods without redundancy. Currently I am passing the entityName and the entityTypeName, which is redundant.

(I am using typescript 4.5.4)

export class Afs {
  public static oneMethod(entityName: Afs.EntityName) {
    // This method just uses the entityName.
  }

  public static someMethod(entityTypeName: Afs.EntityTypeName) {
    // This method uses the entityTypeName and
    // the entityName. However, passing both
    // feels redundant. How to get the entityNames 
    // here, without passing the entityName argument?
  }

}

export namespace Afs {

  export enum EntityName {
    // Simplified. Much more entitynames.
    address = 'address',
    condition = 'condition',
  }

  /* In Java I would create an EntityTypeName 
  ** interface that is implemented by the enums 
  ** with a static method getEntityName() */
  export type EntityTypeName = EntityTypeName.Address 
  | EntityTypeName.Condition


  // I would implement the interface here.
  export namespace EntityTypeName {
    export enum Address {
      default = 'default',
      // enum method from interface 
      // getEntityName() { return Afs.EntityName.Address }
    }
  
    export enum Condition {
      // Simplified. Much more 
      conditionDetails = "conditionDetails",
      employmentConditions = "employmentConditions",
    }  
  }

}

Update

I tried the solution by @mike-clark and this works! (Thanks!!!) It looks however like lot of code duplication, I had some hope that there would be a more simple solution. Real world has got 29 entityNames and 350+ entityTypeNames.

export class Afs {
  public static oneMethod(entityName: Afs.EntityName) {
    console.log(entityName.get());
  }
  public static someMethod(entityTypeName: Afs.EntityTypeName.Generic) {
    console.log(entityTypeName.getEntityName().get());
    console.log(entityTypeName.get());
  }
}

export namespace Afs {

  export class EntityName {
    public static address = new EntityName('address');
    public static condition = new EntityName('condition');
    private constructor(private entityName: string) { }
    public get(): string {
      return this.entityName
    }
  }

  export namespace EntityTypeName {

    export interface Generic {
      get(): string;
      getEntityName(): EntityName;
    }

    export class Address implements Generic {
      public static default = new Address(EntityName.address, 'default');
      private constructor(private entityName: EntityName, private entityTypeName: string) {}
      get(): string { return this.entityTypeName }
      getEntityName(): EntityName { return this.entityName }
    }

    export class Condition implements Generic {
      public static conditionDetails = new Condition(EntityName.condition, 'conditionDetails');
      public static employeeConditions = new Condition(EntityName.condition, 'employeeConditions')
      private constructor(private entityName: EntityName, private entityTypeName: string) {}
      get(): string { return this.entityTypeName }
      getEntityName(): EntityName { return this.entityName }
    }

  }

}
like image 239
Martijn Burger Avatar asked Oct 28 '25 07:10

Martijn Burger


1 Answers

You can't add methods that are callable on enum members in TypeScript. Although it's disappointing (in an object-oriented sense), the practical solution usually used is external static utility functions which switch on the enum and handle all cases.

You can attach static methods to the enum, which is a convenient way to group and keep track of the utility functions that help you work with the enum "metadata".

You can implement an enum-style pattern that allows for methods callable on enum members. But these are not first-class TypeScript enums, though in most cases they are a sufficient replacement. It does feel disappointing to abandon the built-in enum for something that you cook up yourself, but this is a pattern that people do use.

The current technical reason is that the value attached to each enum key is a string literal, which has no common prototype shared by only the key values of the enum. Given this TypeScript:

enum MyEnum {
    A='A',
    B='B'
}

The JavaScript code generated is:

var MyEnum;
(function (MyEnum) {
    MyEnum["A"] = "A";
    MyEnum["B"] = "B";
})(MyEnum || (MyEnum = {}));

As you can see there is no common prototype or class for the member values, so we cannot "hack" our way into adding functions the member values unless we extend the global String class itself, which is a Bad Idea. TypeScript would need to complicate the design of enums by wrapping the member value strings in a second class with a common prototype (or at least a common interface -- express or implied), and I guess the language designers didn't want to introduce that level of complexity.

like image 59
Mike Clark Avatar answered Oct 31 '25 01:10

Mike Clark



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!