skip to Main Content

In TypeScript it’s possible to set the type of a parameter using literals, for example:

type Allowed = "fizz" | "buzz";
 
class FizzBuzz {
  public identity(x: Allowed) {
    return x;
  }
}
 

This means the following snippet of code will give an error even before the code is run:

let fizzBuzz = new FizzBuzz()
fizzBuzz.identity("not-fizz-not-buzz");

In my scenario, however, the literals are not know when the class is defined but they are known when the class is used to instantiate an object. In other words, they are expected as a parameter to the constructor itself. For example:

class FizzBuzz {
  constructor(allowed: string[]) {
    this.allowed = allowed;
  }

  public identity(x: this.allowed) {
    return x;
  }
}

let fizzBuzz = new FizzBuzz(["fizz", "buzz"])

I would like my code to still benefit from TypeScript so that if my next line is fizzBuzz.identity("not-fizz-not-buzz"); I see an error even before the code is executed and should see the autocomplete suggestions.

2

Answers


  1. In your second code snippet, you can see the autocomplete suggestions at design time, while editing the code. But how can you expect to see autocomplete suggestions for something that is known only at runtime, when there is not necessarily any code editor open?

    The best I can imagine is logging an error that contains the suggestions, and you don’t even need Typescript for that:

    class FizzBuzz {
      constructor(allowed) {
        this.allowed = allowed;
      }
      identity(x) {
        if (this.allowed.includes(x))
          return x;
        else {
          var err = new Error(`Identity ${x} not allowed`);
          err.allowedIdentities = this.allowed;
          throw err;
         }
      }
    }
    let fizzBuzz = new FizzBuzz(["fizz", "buzz"]);
    fizzBuzz.identity("not-fizz-not-buzz");
    

    When this is run, it logs the following error:

    Error: Identity not-fizz-not-buzz not allowed
        at FizzBuzz.identity (C:nodejstest.js:9:17)
        at ... {
      allowedIdentities: [ 'fizz', 'buzz' ]
    }
    

    (Added after reading apokryfos’s answer:) It matters when exactly the literals are known. You write "when the class is used to instantiate an object", does this mean

    • when code is written that instantiates the class or
    • when code is executed that instantiates the class, such as new FizzBuzz(getListOfLiterals())?

    In the first case, apokryfos’s method is preferable. But it cannot handle the second case.

    Login or Signup to reply.
  2. In this case you could use generics:

    class FizzBuzz<T extends string> {
      private allowed: T[];
    
      constructor(allowed: T[]) {
        this.allowed = allowed;
      }
    
      public identity(x: T) {
        return x;
      }
    }
    
    
    let fizzBuzz = new FizzBuzz<'fizz'|'buzz'>(["fizz", "buzz"])
    
    
    fizzBuzz.identity("not-fizz-not-buzz"); // Error
    

    This will not be possible if the values of allowed are not known at compile time

    If you are concerned about the duplication then you can let TS do some of the work for you:

    class FizzBuzz<T> {
      private allowed: readonly T[];
    
      constructor(allowed: readonly T[]) {
        this.allowed = allowed;
      }
    
      public identity(x: T) {
        return x;
      }
    }
    
    const allowed = ["fizz", "buzz"] as const; 
    // allowed is now of type readonly [ 'fizz', 'buzz' ]
    let fizzBuzz = new FizzBuzz<typeof allowed[number]>(allowed);
    // typeof allowed[number] should be 'fizz'|'buzz' 
    
    fizzBuzz.identity("not-fizz-not-buzz"); // Error
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search