typescriptfunctional-programmingtypescript-typingstypescript-namespace

working with namespace for enum in typescript


I am working on angular project so we are using typescript here.

I have an enum for statuses

export enum Status {
  ACTIVE = 'ACTIVE',
  DEACTIVE = 'DEACTIVE'
}

Here the properties/fields are duplicated because it helps in serialization/deserialization and without that Status.ACTIVE becomes 0 for ordinality.

Now for display purposes, we need to have mapping of presentable words, like, 'ACTIVE'-> 'Active'.

For this we used namespaces,

export namespace Status {
  export function displayName(s: Status){
    const mapping = {Status.ACTIVE: 'Active', ...}; // I know this can declared at some other places so we don't need to re-initialize every time this function is called.  
    return mapping[s];
  }
}

This makes the usage of Status very easy. Because now I can have Status.ACTIVE as well as Status.displayName(s). Basically using same "classname" status. (of course both are defined in status.ts file)

Now since default lint suggest to remove namespaces.

'namespace' and 'module' are disallowed (no-namespace)

I understand that namespace was created when ES6 module system was not in place. So now classes make more sense than namespace. But in my case - enum's context

Questions are,

  1. Is usage of namespace create any performance, optimization, or any other issue?
  2. Is there a better way of accomplish the same thing (using the same "class" name)
  3. Not creating namespace and exporting another function which works the same way - function in same file but without namespace; Is that the solution? And essentially not satisfyingly my original requirement of having same class name.

Like following

status.ts

export Status {
  ACTIVE = 'ACTIVE'
}
export function statusToDisplayName(s: Status){
  const map = {...};
  return map[s];
}

usage.ts
import {Status, statusToDisplayName} from 'some/path/status';
...
status = Status.ACTIVE;
statusToDisplay = statusToDisplayName(status);

Solution

  • Namespaces vs ES modules

    Namespaces are considered deprecated in favor of ES modules. It does not cause any performance or optimization issues, but definitely cause some confusion when used with ES modules.

    Detailed response

    ES module as a namespace

    You can use ES module as a namespace to achieve similar result

    // Status.ts
    export enum Status {
      ACTIVE = 'ACTIVE',
      DEACTIVE = 'DEACTIVE'
    }
    
    export function toDisplayName(s: Status){
      const map = {...};
      return map[s];
    }
    
    // some-other-module.ts
    import * as StatusModule from './Status.ts'
    
    const active = StatusModule.Status.ACTIVE;
    const displayname = StatusModule.toDisplayName(active);
    

    If you want statuses to be 'on the same level' with toDisplayName function you can consider following option

    // Status.ts
    enum Status {
      ACTIVE = 'ACTIVE',
      DEACTIVE = 'DEACTIVE'
    }
    
    export const ACTIVE = Status.ACTIVE;
    export const DEACTIVE = Status.DEACTIVE;
    export function toDisplayName(...){...}
    

    But I don't recommend it, because it will result in code duplication

    Alternative

    To keep desired structure you can try to declare a class with static props

    // Status.ts
    enum StatusName {
      ACTIVE = 'ACTIVE',
      DEACTIVE = 'DEACTIVE'
    }
    
    export abstract class Status {
      static readonly ACTIVE = StatusName.ACTIVE
      static readonly DEACTIVE = StatusName.DEACTIVE
      
      static toDisplayName(status: StatusName) {...}
    }
    

    This way you will have nice autocompletion when importing Status, but will have some unnecessary props showing up using IntelliSense for Status.

    P.S.

    Maybe your toDisplayName function can be a little bit optimized to prevent some additional work on adding new statuses

    If you need a status name capitalized, you can declare capitalize function in helpers or utils

    // helpers/capitalize.ts
    export const capitalize = (str: string) => {
      const firstLetter = str.charAt(0).toLocaleUpperCase();
      const rest = str.slice(1).toLocaleLowerCase();
    
      return firstLetter.concat(rest);
    }
    
    // Status.ts
    export const toDisplayName = (status: StatusName) => capitalize(status);