2

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 }
    }

  }

}
Martijn Burger
  • 7,315
  • 8
  • 54
  • 94

1 Answers1

3

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.

Mike Clark
  • 10,027
  • 3
  • 40
  • 54
  • I tried adding the static methods to the enums, but this does not work for me, as they are not implemented on the type. Second solution is looks like a valid solutions which I will tty, but still has a lot of duplication. Every EntityTypeName enum has one EntityName, but needs to be duplicated for every value. But I guess it's the best we can do with typescript atm. – Martijn Burger Jan 15 '22 at 17:48