I am trying to create a class with a method whose return type will depend on the boolean
value of a flag-parameter
I have managed to do this successfully when
- using a plain function expression that implements an interface,
- or in a class when declaring the method overloads directly in the class body
However, I am not able to do this when I am trying to use an interface that is implemented by the class.
I would like to do this using interface implementation for a class method to achieve the dynamic return type.
Thanks!
Here is the example code which is also available in the Typescript Playground
// it works with interface for plain function
interface common {
<T extends boolean>(flag: T): T extends true ? string : boolean ;
(flag: boolean): string | boolean;
}
const method: common = (flag: boolean) => {
if (flag) {
return 'truhty';
} else {
return false ;
}
}
// it works with plain method overload
function test(flag: true): string
function test(flag: false): boolean
function test(flag: boolean): string | boolean {
if (flag) {
return 'truthy'
} else {
return false;
}
}
const x = test(true);
const y = test(false);
// This works with direct class method overload
class Exp {
test(flag: true): string
test(flag: false): boolean
test(flag: boolean): string | boolean {
if (flag) {
return 'truthy'
} else {
return false;
}
}
}
const val = new Exp().test(false); // boolean!
const val2 = new Exp().test(true); // string!
// It does not work in class with interface overload
interface common2 {
test(flag: true): string
test(flag: false): boolean
test(flag: boolean): string | boolean
}
class Exp2 implements common2 {
test(flag: boolean) {
if (flag) {
return 'truthy'
} else {
return false;
}
}
}
// it is not working in class with generic conditional type
interface common3 {
test<T extends boolean>(flag: T): T extends true ? string: boolean
test(flag: boolean): string | boolean
}
class Exp3 implements common3 {
test(flag: boolean) {
if (flag) {
return 'truthy';
} else {
return false;
}
}
}
const val3 = new Exp3().test(false); // WRONG false | 'truthy'!
const val4 = new Exp3().test(true); // WRONG false | 'truthy'!
// it does not work with conditional directly in class
class Exp4 {
test<T extends boolean>(flag: T): T extends true ? string : boolean
test(flag: boolean) {
if (flag) {
return 'truthy';
} else {
return false;
}
}
}
const val5 = new Exp3().test(false); // WRONG false | 'truthy'!
const val6 = new Exp3().test(true); // WRONG false | 'truthy'!
CodePudding user response:
I don't think you get a choice but to repeat the overload in the class (as you did with Exp
) when implementing the interface; here's a modified Exp2
that both implements the interface and lists the overloads:
class Exp2 implements common2 {
test(flag: true): string;
test(flag: false): boolean;
test(flag: boolean): string | boolean {
if (flag) {
return 'truthy'
} else {
return false;
}
}
}
If you don't, the class method isn't a match for the interface definition. The interface is a contract your class must match, not a blueprint that affects how the class's declarations are interpreted.
CodePudding user response:
I actually found the answer I was looking for on how to use conditional typing on a class method with an interface.
It seems that you can use the interface with its conditional generic, without creating all the possible overloads, as long as you type the returned values from the implementation.
So according to my original post, this would be:
type ResType<T extends boolean> = T extends true ? string : boolean;
interface common3 {
test<T extends boolean>(flag: T): ResType<T>
}
class Exp3 implements common3 {
test<T extends boolean>(flag: T): ResType<T> {
if (flag) {
return 'truthy' as ResType<T>;
} else {
return false as ResType<T>;
}
}
}
const val3 = new Exp3().test(false); // boolean
const val4 = new Exp3().test(true); // string