Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

TypeScript interface downcasting

I have an interface which is extending another one. Now I am trying to put a variable of the superinterface type into a function that needs a subinterface type parameter. This is not possible as the superinterface typed variable is missing certain attributes.

It's a little hard to explain, so I've created an example with an Animal interface, which is extended by a Horse interface:

interface Animal {
    age:number;
    //... 100 other things
}

interface Horse extends Animal {
    speed:number;
}

Now I have a module with one private function (rideHorse), which only accepts a Horse as parameter. And a public function (rideAnimal), accepting all Animals like below:

module AnimalModule {
    function rideHorse(horse:Horse) {
        alert(horse.speed);
    }

    export function rideAnimal(animal:Animal) {
        animal.speed = 10; // Error: Animal cannot have speed
        rideHorse(animal); // Error: 'animal' needs speed
    }
}

// Calling the rideAnimal function
var myAnimal:Animal = {
    age: 10
};
AnimalModule.rideAnimal(myAnimal);

As you can see this is not working because the animal parameter of rideAnimal doesn't have speed. So my question is: How can I cast my animal into a Horse and add speed manually inside the rideAnimal function, so the errors will disappear?

like image 358
Duncan Luk Avatar asked Apr 23 '16 10:04

Duncan Luk


2 Answers

You can cast your Animal into a Horse like this.

function rideAnimal(animal:Animal) {
    var horse = animal as Horse;
    horse.speed = 10;
    rideHorse(horse);
}
like image 171
Valéry Avatar answered Sep 19 '22 17:09

Valéry


Yes, you can use user defined type guards to handle this kind of custom type evaluation:

interface Animal
{
    age: number;
    //... 100 other things
}

interface Horse extends Animal
{
    speed: number;
}

module AnimalModule
{
    function rideHorse(horse: Horse)
    {
        alert(horse.speed);
    }

    function isHorse(a: Animal): a is Horse
    {
        return "speed" in a;
    }

    export function rideAnimal(animal: Animal)
    {
        if (isHorse(animal))
        {
            animal.speed = 10; // Ok
            rideHorse(animal); // Ok
        } else
            throw new Error("You can only ride horses")
    }
}

If Horse and Animal were classes, you could have just done if (animal instanceof Horse) instead of using the custom type guard isHorse

like image 24
Alex Avatar answered Sep 22 '22 17:09

Alex