An Additional Generics Example

Explore an additional example of using TypeScript generics to see how they can also be used to restrict the types that certain functions allow.

In this lesson, we’ll see how generics can help restrict the parameter types accepted by functions. Let’s start by defining a simple class called Animal.

class Animal {
public legCount: number;
constructor(legCount: number) {
this.legCount = legCount
}
}

We’ll then define two more classes, Cat and Kangaroo that extend class Animal:

class Cat extends Animal {
constructor() {
super(4);
}
}
class Kangaroo extends Animal {
constructor() {
super(2);
}
}

Finally, let’s make a Bacteria class, which does not extend the Animal class. We can leave it empty for now:

class Bacteria {
}

Let’s imagine we want to write a function legPrinter that prints the legs of any animal. We don’t want to write a separate function for, say, a printCatLegCount and a printKangarooLegCount.

We just want one function. Again, generics provides a way for our code to accept types that have a legCount and can be used by our printLegCount function. Let’s start by writing the skeleton of our printLegCount function.

function printLegCount() {
}

The extends operator

In this situation, passing a plain generic type <T> to print isn’t enough. We don’t want just any type to be accepted by printLegCount. Luckily, TypeScript generics support the built-in JavaScript operator extends. We can use it to include any type T that extends the Animal class. In other words, we can allow any type T that is a child or subclass of the Animal class. The syntax looks like this:

function printLegCount<T extends Animal>(animal: T) {
}

Finally, filling out the body of printLegCount, we have the complete typed function:

function printLegCount<T extends Animal>(animal: T) {
console.log(animal.legCount)
}

Testing printLegCount

We can test the Intellisense by passing an instance of each of our Cat, Kangaroo, and Bacteria classes into printLegCount, and seeing if TypeScript complains. Check out the interactive example of everything we have written in this lesson:

class Animal {
public legCount: number;
constructor(legCount: number) {
this.legCount = legCount
}
}
class Cat extends Animal {
constructor() {
super(4);
}
}
class Kangaroo extends Animal {
constructor() {
super(2);
}
}
class Bacteria {
}
function printLegCount<T extends Animal>(animal: T) {
console.log(`My leg count is: ${animal.legCount}`);
}
const myCat = new Cat();
const myKangaroo = new Kangaroo();
// Both fine: myCat and myKangaroo are of the Cat and Kangaroo
// type respectively, which both extend Animal
printLegCount(myCat);
printLegCount(myKangaroo);
const myBacteria = new Bacteria();
// TypeScript complains here, since the Bacteria class does not extend animal!
// Comment out this line to see the output of the two function calls above.
//printLegCount(myBacteria);
All the code from this lesson showcasing the extends keyword in TypeScript

So, not only does our function printLegCount work correctly from a functional standpoint because it correctly prints the legCount of a given Animal, it also works from a type perspective by accepting only parameters of types that extend the Animal class!

Conclusion

In this chapter, we scaffolded our application with create-react-app's TypeScript template. This gave us a good playground to work in, which we’ll use in upcoming lessons to learn more about generics. Then, we took our first look at generics in TypeScript by building a basic generic sortByKey function. Finally, we learned that generics can also be used to restrict the types that certain functions allow.

TypeScript generics basics, extends, and generics advantages

1

What is one way of phrasing T extends Animal?

A)

All T types that are of a child class or subclass of the Animal class.

B)

All T types that implement functionality beyond the Animal class

C)

All T types that work as a plugin to class Animal, extending class Animal's abilities and behaviour.

Question 1 of 30 attempted