How to Create Abstract Classes in TypeScript

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

Introduction

TypeScript, as a superset of JavaScript, provides more rigorous structuring characteristics, one of which is the ability to declare abstract classes. Abstract classes serve as a blueprint for other classes, allowing for code reusability and the establishment of contract-like structures that must be followed by derivative classes.

What is an Abstract Class?

An abstract class is a class that cannot be instantiated directly and is often used as a base class for other classes. Abstract classes can define abstract methods, which are methods without a body that must be implemented in derived classes, as well as concrete methods that have an implementation.

abstract class Shape {
  abstract getArea(): number;

  printArea(): void {
    console.log(`The area is ${this.getArea()}.`);
  }
}

Defining an Abstract Class

To define an abstract class in TypeScript, you use the abstract keyword. Here’s a simple example of an abstract class that defines an abstract method and a concrete method.

abstract class Vehicle {
  constructor(public make: string, public model: string) {}

  abstract startEngine(): void;

  honk(): void {
    console.log('Beep beep!');
  }
}

Implementing an Abstract Class

To create a class based on an abstract class, you must implement all its abstract methods and can optionally override concrete methods.

class Car extends Vehicle {
  startEngine(): void {
    console.log(`${this.make} ${this.model} engine started.`);
  }
}

Advanced Abstract Class Patterns

Abstract classes can also include abstract properties, getters, setters, and complex method signatures. Here’s a more complex example utilizing these features:

abstract class Employee {
  abstract get employeeId(): number;
  abstract set employeeId(value: number);

  constructor(public name: string) {}

  abstract workOnTask(taskId: string): void;

  fileReport(report: string): void {
    console.log(`${this.name} has filed a report: ${report}`);
  }
}

class Developer extends Employee {
  private _employeeId: number;

  get employeeId(): number {
    return this._employeeId;
  }

  set employeeId(value: number) {
    this._employeeId = value;
  }

  workOnTask(taskId: string): void {
    console.log(`${this.name} is working on task #${taskId}.`);
  }
}

Using Abstract Classes with Interfaces

Abstract classes and interfaces can work together, with interfaces declaring the shape of an object, while abstract classes provide partial implementation.

interface IPerson {
  name: string;
  greet(): void;
}

abstract class Person implements IPerson {
  constructor(public name: string) {}

  abstract greet(): void;

  describe(): void {
    console.log(`This is ${this.name}.`);
  }
}

Practical Considerations

When designing abstract classes, it’s important to decide which members should be abstract and which should be concrete. This choice can heavily influence the architecture of derived classes and the reusability of code.

Limitations of Abstract Classes

Despite the powerful abstraction they provide, abstract classes cannot be instantiated directly and sometimes introduce complexity when large hierarchies are involved.

Conclusion

In conclusion, TypeScript’s abstract classes are a potent feature that can help you create a more structured and maintainable codebase. By enforcing consistent methods and properties across related classes, abstract classes facilitate development and can improve coding efficiency and collaboration among developers.