Search code examples
javascripttypescriptoopecmascript-6typing

Call method on the element of the array of the different instances in typescript?


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?


Solution

  • 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.