I am using typescript and faced such a problem. I have a list of classes and array of its instances. These classes extends from some basic class. Every class has own methods. I want to call these own methods on element of the array. But right i got an error.
Here is an example:
class BasicClass {
getName() {
console.log('My name is BasicClass');
}
}
class Child1 extends BasicClass {
getChildOneSpecialName() {
console.log('My getChildOneSpecialName is Child1');
}
}
class Child2 extends BasicClass {
getSpecialName() {
console.log('My special name is Child2');
}
}
class Child3 extends BasicClass {
getStrangeName() {
console.log('My getStrangeName is Child1');
}
}
const array = [new Child1(), new Child2(), new Child3()];
array[1].getSpecialName(); // Property 'getSpecialName' does not exist on type 'Child1 | Child2 | Child3'.
array[2].getStrangeName(); // Property 'getStrangeName' does not exist on type 'Child1 | Child2 | Child3'.
What should I do to make it possible?
Well, in your example, the only way is to make a cast for every item. If you want to call the common method for any of them, you can define your array as:
const array: Array<BasicClass> = [new Child1(), new Child2(), new Child3()];
array[1].getName(); // correct
However, TypeScript now doesn't know that a specific index has a value of class Child1
, so it doesn't let you call the method. In your case, you'd better cast every time:
(<Child2>array[1]).getSpecialName()
or you can define the array as any[]
:
const array: any[] = [new Child1(), new Child2(), new Child3()];
array[1].getSpecialName(); // correct
This lets you do anything you want, under your own responsibility, but you also lose the compiler's help.
From the talk to T.J I going to point a few questions about type inference in this case.
As T.J. pointed, there is really not need to declare array
as Array<BasicClass>
(or BasicClass[]
, which is the same). If you don't, TypeScript will asume the type of the array to be (Child1 | Child2 | Child3)[]
.
In this case, if you do array[0].getName()
it works well. In both cases, TypeScript knows that every element of the array have a method getName
. If you don't specify the array's type, it knows because Child1
, Child2
and Child3
have such a method (not because they extend BasicClass
).
However, let's imagine that I define two new classes:
class Child4 extend BasicClass {
getAnotherName() {
console.log('anything');
}
}
class Other {
getName() { console.log('other'); }
getStrangeName() { }
}
Now we define the arrays in both ways:
const array1: BasicClass[] = [new Child1(), new Child2(), new Child3()];
const array2 = [new Child1(), new Child2(), new Child3()];
array1[0].getName(); // fine
array2[0].getName(); // fine
However, let's do this:
array1.push(new Child4()); // fine. Child4 is a subclass of BasicClass
array1.push(new Child4()); // error! Child4 is not compatible with (Child1 | Child2 | Child3)!
And if we do:
array1.push(new Other()); // Works! Why??
array2.push(new Other()); // also works.
If you're wondering why the last example works in both cases, is due to the fact that Other
is both compatible with BasicClass
(it has all its methods and properties with the same signatures). And it is also compatible with (Child1 | Child2 | Child3)
because it is structurally compatible with Child3
.
However, if you remove the getStrangeName
method from Other
, you can still assign it to BasicClass[]
, because it is still structurally compatible with BasicClass
. However, assigning it to array2
will fail, because it is not compatible with either Child1
, Child2
or Child3
.
So, in the end, what matters with types in TypeScript is the structure of the type, not the name or whether they derive from each other.