Class Inheritance in TypeScript: A Complete Guide

Updated: January 7, 2024 By: Guest Contributor Post a comment

Introduction

TypeScript provides a robust foundation for object-oriented programming, including the use of class inheritance. This tutorial delves into the principles of class inheritance in TypeScript with practical examples.

Understanding Basics of Class Inheritance

In object-oriented programming, inheritance enables new classes to receive, or inherit, properties and methods from existing classes. Let’s consider a simple class in TypeScript:

class Animal {
  constructor(public name: string) {}
  move(distanceInMeters: number): void {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

This Animal class has a name and a method to move. We can subclass it to create more specific types of Animals.

class Snake extends Animal {
  move(distanceInMeters = 5): void {
    console.log('Slithering...');
    super.move(distanceInMeters);
  }
}

class Horse extends Animal {
  move(distanceInMeters = 45): void {
    console.log('Galloping...');
    super.move(distanceInMeters);
  }
}

Snake and Horse inherit from Animal and also define their move behavior. Notice the use of super.move(); it calls the move method on the base class, Animal.

Advanced Inheritance Patterns

We can also have abstract classes, which are base classes from which other classes may be derived, but cannot be instantiated on their own.

abstract class Quadruped {
  abstract makeSound(): void;
  walk(): void {
    console.log('Walking on all fours.');
  }
}

class Dog extends Quadruped {
  makeSound(): void {
    console.log('Woof! Woof!');
  }
}

In this case, a subclass of Quadruped must implement makeSound since it’s an abstract method.

Tackling the Diamond Problem

TypeScript’s class system copy is based on single inheritance, which means each class can only inherit from one other class, effectively avoiding the ‘diamond problem’ of multiple inheritance. However, through interfaces and mixins, we can approximate the behavior of multiple inheritance;

type Constructor = new (...args: any[]) => T;

function Flyable(Base: TBase) {
  return class extends Base {
    fly() {
      console.log('Flying!');
    }
  };
}

class Bird extends Flyable(Animal) {}

let pigeon = new Bird('Pigeon');
 pigeon.fly(); // Flying!
 pigeon.move(10); // Pigeon moved 10m.

Here, a Flyable mixin provides flying functionality to the Bird class, which continues to inherit from the base Animal class.

Interfaces and Implementation

Interfaces in TypeScript can be used to define contracts within your code and are a way to ensure that a class meets a particular contract.

interface IFlyable {
  fly(): void;
}

class Helicopter implements IFlyable {
  fly() {
    console.log('Helicopter is flying.');
  }
}

This ensures that any Helicopter class will have a fly method implementation.

Generics and Inheritance

Generics provide a way to make classes, functions, and interfaces work with any data type. Combined with inheritance, generics promote reusability and type safety.

class Cage {
  contents: T;
  constructor(contents: T) {
    this.contents = contents;
  }
}

class Lion extends Animal {}

let lionCage = new Cage(new Lion('Simba'));
console.log(lionCage.contents.name); // Simba

The Cage class is generic and works with any type. We can then define a Lion cage that can only contain Lion instances, preventing runtime errors.

Decorators and Class Inheritance

Decorators are a stage 2 proposal for JavaScript and are available in TypeScript. They can be used to modify classes and class members’ behaviors at design time.

function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}

The @sealed decorator prevents new properties from being added to the Greeter class, and its prototype, and marks them as non-configurable.

Summary

TypeScript’s class inheritance allows developers to build a structured, maintainable, and scalable codebase. This guide explored the basic and advanced patterns of inheritance, abstract classes, mixins, interfaces, generics, and decorators.