I wan't to describe an abstract method in abstract class which can take number
or string
and also return number or string;
I'm using |
symbol to tell method that it's arguments and return types may vary from string to number.
Then I'm creating two classes b
and c
which are extended from abstract class a
and trying to override method test()
with no argument and return type variation.
Next, I'm declaring variable x
which type could be similar to b
or c
class, and I'm creating instace of one of those classes depending on random statement.
And finally I'm trying to call test()
method, but TS piler giving me error described below.
abstract class a {
abstract test(x: string | number): string | number;
}
class b extends a {
test(x: number): number {
return x;
}
}
class c extends a {
test(x: string): string {
return x;
}
}
let x: b | c;
if (Math.random() > 0.5) {
x = new b()
} else {
x = new c()
};
x.test(1)
Here is error from TS piler:
Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => number) | ((x: string) => string)' has no patible call signatures.
(property) test: ((x: number) => number) | ((x: string) => string)
Perhaps I'm using wrong aproach or I misunderstood TS documentation, if so - could you please point me out better way of my goal.
Sorry for poor class names and absence of any "fiddle" - I coudn't find any js playground websites which highlights TS piler error, so I remend official TS Playground
I wan't to describe an abstract method in abstract class which can take number
or string
and also return number or string;
I'm using |
symbol to tell method that it's arguments and return types may vary from string to number.
Then I'm creating two classes b
and c
which are extended from abstract class a
and trying to override method test()
with no argument and return type variation.
Next, I'm declaring variable x
which type could be similar to b
or c
class, and I'm creating instace of one of those classes depending on random statement.
And finally I'm trying to call test()
method, but TS piler giving me error described below.
abstract class a {
abstract test(x: string | number): string | number;
}
class b extends a {
test(x: number): number {
return x;
}
}
class c extends a {
test(x: string): string {
return x;
}
}
let x: b | c;
if (Math.random() > 0.5) {
x = new b()
} else {
x = new c()
};
x.test(1)
Here is error from TS piler:
Cannot invoke an expression whose type lacks a call signature. Type '((x: number) => number) | ((x: string) => string)' has no patible call signatures.
(property) test: ((x: number) => number) | ((x: string) => string)
Perhaps I'm using wrong aproach or I misunderstood TS documentation, if so - could you please point me out better way of my goal.
Sorry for poor class names and absence of any "fiddle" - I coudn't find any js playground websites which highlights TS piler error, so I remend official TS Playground
2 Answers
Reset to default 2When you create an instance of the class in a if block then the typescript piler is unable to figure out which type x will be. That is OK, but the problem is that you then try to call the test function with a number and that is only possible when the type is b. Since the piler think there is a possibility that x is of type c, you get an error.
You need to assure the piler that when you call test then the function you call will always match the parameters provided.
You can either:
Change the call signatures so that both accept any type, this way it does not matter to the piler which of the methods are called:
class b extends a { test(x: any) { return x; } } class c extends a { test(x : any) { return x; } }
Call the method within the if block:
if (Math.random() > 0.5) { x = new b(); x.test(1); } else { x = new c(); x.test('1'); }
Typeguard you method call:
if (x instanceof b) x.test(1); else if(x instanceof c) x.test('1');
Check out the handbook on Union types and typeguards: https://www.typescriptlang/docs/handbook/advanced-types.html#union-types.
EDIT: The suggestion for you so that you dont have to typeguard you type on every call, would be to have the type checking done in the methods themselves. The downside of this is that will be able to call the method with an incorrect parameter without getting a warning from the piler. Here is an example of how that could look:
abstract class a {
protected abstract internal(x: any): any;
public test(x: string | number): string | number {
return this.internal(x);
}
}
class b extends a {
protected internal(x) {
if (typeof x === "number")
return x;
else
throw new Error("Invalid argument");
}
}
class c extends a {
protected internal(x) {
if (typeof x === "string")
return x;
else
throw new Error("Invalid argument");
}
}
let x: b | c;
if (Math.random() > 0.5) {
x = new b();
} else {
x = new c();
}
x.test(1);
You are declaring x as either b or c and then try to redefine it by making it only b or only c (as I understand it but my understanding is limited) According to the editor you provided the below code works though:
abstract class a {
abstract test(input: number | string )
}
class b extends a {
test(x: string) {
return x;
}
}
class c extends a {
test(x : number) {
return x;
}
}
let x//: b | c;
if (Math.random() > 0.5) {
x = new b()
} else {
x = new c()
}
x.test(1)