Lets say I have 3 Interfaces:
interface Animal {
height: number
name: string
}
interface Dog extends Animal {
numberOfTeeth: number
hasTail: boolean
}
interface Cat extends Animal {
sizeOfPaw: number
}
and lets say I query an api like this:
function getAnimal(id:string, animalType:string) : Animal {
const res = await (axios.get(`http://animalapi.com/${animalType}/${id}`));
return res.data; // Could be a cat, could be a dog, based on animal type
}
If I try to do:
const animal:Dog = getAnimal("1", "dog"); // Would it be possible to not need to pass the string dog? Perhaps store the "dog" value within the interface and pass it from there?
I get errors that 'Animal is missing the following types', followed by the types that are in Dog.
Is it possible to be able to obtain both Dog and Cat from the same function, without having to duplicate the call?
Yup, this is called type narrowing, and you can do it with a discriminated union that joins the
DogandCatshapes but gives each shape a unique "tag" that distinguishes it from the others (this might be a property likeid,type, or whatever you want to call it):Now, when you go to create an
Animal, TypeScript will expect you to identify the specific shape by specifying thetypeproperty. Then you can pass alonganimal.typewherever you want since it's a string literal:This pattern has the added benefit of allowing you to narrow a generic
Animaltype to a specific sub-type. For example, you can use it to write a type predicate to check if an animal is of a particular constituent type:Without discriminated unions, you'd need to do a type assertion and check a property that you know is absolutely unique to dogs (maybe
hasTail, but that's true for cats too):