Search code examples
angulartypescriptvariablesenumsconstants

constant variables vs enum type in Angular using Typescript


What is best practice when declaring a constant variable for a typescript angular component?

I have used these two ways, but I'd like to know which one is better.

First approach, it's declaring a global constant variable in capital letters before the class or in a separated typescript file. However, I don't really like when I have several of those global constant variables in capital letters through the class component.

const STAGE_1: string = 'stage1';

STAGE_1

Second approach, it's creating an enum in a typescript file which it's imported to the component.

export enum Stage {
  Stage1 = 'stage1',
}

Stage.Stage1

I've been using the second approach lately as I saw it in the typescript handbook. However, I saw the first approach in other languages like java and go.

Any recommendations or amplifications?


Solution

  • There are actually three different approaches:

    Type

    Allows you to specify a type that is restricted to the given values. Since they do not generate any code, they're effectively like enums that exist at compile time, but not at runtime. They're useful when you're dealing with REST APIs that return predefined values; for example:

    type Stage = "stage1" | "stage2";
    
    function fnByType(value: Stage) {
    }
    
    fnByType("stage1"); // OK
    fnByType("stage2"); // OK
    fnByType("stage3"); // Not assignable!
    

    Enum

    Allows you to specify an enum that is restricted to the given values. They do generate code and therefore exist at compile time, and at runtime. Because they exist at runtime, you can enumerate all of their keys and values; for example:

    enum Stage {
        Stage1 = "stage1",
        Stage2 = "stage2"
    }
    
    function fnByEnum(value: Stage) {
    }
    
    fnByEnum(Stage.Stage1); // OK
    fnByEnum(Stage.Stage2); // OK
    fnByEnum("stage1") // Not assignable!
    fnByEnum("stage3") // Not assignable!
    
    console.log(Object.keys(Stage)); // ["Stage1", "Stage2"]
    console.log(Object.values(Stage)); // ["stage1", "stage2"] 
    

    Const

    Allows you to define a new constant (readonly) variable. This is useful when you want to avoid things like "magic values" (values whose purpose is effectively unknown to the reader, but means something in your code), meaning you can better explain what your code is doing. Additionally it supports the DRY (Don't Repeat Yourself) principle, since you can reference the const value, rather than repeating "magic values" in your code. Finally, it means that if you need to update the const value, you don't have to trawl through your code looking for all instances of the value; you just update it once; for example:

    const STAGE1: string = "stage1";
    const STAGE2: string = "stage2";
    
    function fnByString(value: string) {
    }
    
    fnByString(STAGE1); // OK
    fnByString(STAGE2); // OK
    fnByString("stage3"); // OK...but re-read the benefits of NOT using "magic values"
    

    You can use all three concepts interchangeably, and to complement one another; for example:

    type Stage = "stage1" | "stage2";
    
    const STAGE1: string = "stage1";
    const STAGE2: string = "stage2";
    
    function fnByType(value: Stage) {
    }
    
    fnByType(STAGE1); // OK
    fnByType(STAGE2); // OK
    fnByType("stage3"); // Not assignable!
    

    Finally, some food for thought. It would be nice if you could define the const values and then use them to define a type of predefined values, but as far as I can see, the below isn't possible (yet):

    const STAGE1: string = "stage1";
    const STAGE2: string = "stage2";
    
    type Stage = STAGE1 | STAGE2;
    
    function fnByType(value: Stage) {
    }
    
    fnByType(STAGE1); // OK
    fnByType(STAGE2); // OK
    fnByType("stage3"); // Not assignable!