1

Is there a way to declare a method overload with an optional parameter if a class generic is of a certain type? In the below example the goal is to have a class whose run method requires an argument only if the class's generic T is a number. What I've written doesn't work: I think the T in the run declaration is interpreted as a different generic than Test's generic T. Is there a way to accomplish this?


class Test<T extends string | number> {
  run<T extends number>(a: number): void
  run<T extends string>(a?: number): void
  run(a: number): void {
    console.log(a)
  }
}

const a = new Test<number>();
a.run() // This should be an error, as a's generic is a number and thus its run method should require an argument.

const b = new Test<string>();
b.run() // OK, since b's generic is a string.

2 Answers 2

7

A conditionally-typed rest parameter is a useful choice for your method in this case:

TS Playground

class Test <T extends string | number> {
  run (...args: T extends number ? [a: number] : [a?: number]): void {
    const [a] = args;
    console.log(a); // number | undefined
  }
}

const a = new Test<number>();
a.run(42); // ok
a.run(); /*
  ^^^^^
Expected 1 arguments, but got 0.(2554) */

const b = new Test<string>();
b.run(42); // ok
b.run(); // ok
Sign up to request clarification or add additional context in comments.

Comments

1

I've been trying to find a way to accomplish this using only one classes, but i'm not sure it's possible. It makes sense, since having the same function called in a different way is not very clean.

What you could do, is use an interface, a parent class and multiple child class. This would allow you to use one or the other, and stay DRY at the same time. Here is my take on this problem.

// we create an interface with an optional parameter "a"
interface ITest {
  run(a?: number) : void;
}

// we create a super class that will handle the process of the "run" function.
// this class has a function with a necessary parameter
abstract class AbstractTest<T extends string|number> {
  run(a: T) {
    console.log('ran with param: ', a);
  }
}

// We create two sub class, one for the run with no parameter, one with the run with one parameter. 
// Both classes implements the interface and calls the parent function "run".

// this class does not extends the "run" function, since it is the same as the parent one
class TestNumber<T extends number> extends AbstractTest<T> implements ITest {}

// this class extends the "run" function, since it needs to process the fact that there is no parameters passsed
// to the "run" function. I've used a default value here, but it will depend on your use case.
class TestString<T extends string> extends AbstractTest<T> implements ITest {
  public static readonly DEFAULT_VALUE = 'some value';

  run() : void {
    super.run(TestString.DEFAULT_VALUE as T)
  }
}

// we create new objects based on the need.
const a = new TestNumber<number>();
const b = new TestString<string>();

// this function call works since we've passed a parameter
a.run(42);
// this one does not work and an error is thrown.
a.run()

// this function call works great without an argument.
b.run()

I've placed comments to explain the changes. here is a typescript playground with the working code

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.