- TypeScript Tutorial
- TypeScript Home
- TypeScript Introduction
- TypeScript Setup
- TypeScript First Program
- TypeScript vs JavaScript
- TypeScript Data Types
- TypeScript Type Inference
- TypeScript Type Annotations
- TypeScript Interfaces
- TypeScript Enums
- TypeScript Type Aliases
- TypeScript Type Assertions
- TypeScript Variables
- TypeScript Functions
- TypeScript Functions
- TypeScript Optional Parameters
- TypeScript Default Parameters
- TypeScript Rest Parameters
- TypeScript Arrow Functions
- Classes and Objects
- Introduction to Classes
- Properties and Methods
- Access Modifiers
- Static Members
- Inheritance
- Abstract Classes
- Interfaces vs Classes
- Advanced Types
- TypeScript Union Types
- TypeScript Intersection Types
- TypeScript Literal Types
- TypeScript Nullable Types
- TypeScript Type Guards
- TypeScript Discriminated Unions
- TypeScript Index Signatures
- TypeScript Generics
- Introduction to Generics
- TypeScript Generic Functions
- TypeScript Generic Classes
- TypeScript Generic Constraints
- TypeScript Modules
- Introduction to Modules
- TypeScript Import and Export
- TypeScript Default Exports
- TypeScript Namespace
- Decorators
- Introduction to Decorators
- TypeScript Class Decorators
- TypeScript Method Decorators
- TypeScript Property Decorators
- TypeScript Parameter Decorators
- Configuration
- TypeScript tsconfig.json File
- TypeScript Compiler Options
- TypeScript Strict Mode
- TypeScript Watch Mode
Abstract Classes in TypeScript
In TypeScript, abstract classes serve as "blueprints for other blueprints." Unlike regular classes that you can use to create objects immediately, an abstract class is intentionally incomplete. It allows you to define a common structure, shared logic, and mandatory methods that every subclass must implement, ensuring consistency across your codebase.
Think of an abstract class as a template. For example, in a payment system, you might have a generic PaymentProcessor. You can't just "process a payment" without knowing the method (Credit Card, PayPal, etc.), but every processor will share certain traits like a transaction ID or a validate() method.
Key Points About Abstract Classes:
- Cannot be instantiated: You cannot use the
newkeyword on an abstract class. It exists only to be inherited. - Abstract methods: These are method signatures without a body. They act as a "contract" any non-abstract subclass must provide its own logic for these methods.
- Regular methods: Unlike Interfaces, abstract classes can contain fully implemented methods that subclasses can use or override.
- Member Visibility: You can use access modifiers like
private,protected, andpublicto control how subclasses interact with the base data.
const myItem = new BaseItem();. This will cause a TypeScript compiler error.
Declaring an Abstract Class
To define an abstract class, simply prefix the class keyword with abstract. To define an abstract method, place abstract before the method name and omit the function body (the curly braces).
Example:
abstract class Animal {
// Shared property for all animals
constructor(public name: string) {}
// Abstract method: Every animal makes a sound, but in different ways
abstract speak(): void;
// Regular method: All animals breathe the same way in this logic
breathe(): void {
console.log(`${this.name} is breathing...`);
}
}
class Dog extends Animal {
// Implementing the required abstract method
speak(): void {
console.log(`${this.name} barks: Woof! Woof!`);
}
}
const myDog = new Dog("Buddy");
myDog.speak(); // Output: Buddy barks: Woof! Woof!
myDog.breathe(); // Output: Buddy is breathing...
In this example:
- The
Animalclass provides thebreathe()logic so we don't have to rewrite it for every animal. - The
Dogclass is forced to implementspeak(), otherwise, the code won't compile. - We gain the benefit of polymorphism: we can treat different animals as the generic type
Animalwhile calling their specificspeak()behaviors.
Abstract Methods
Abstract methods are powerful because they guarantee that a specific functionality exists without the base class needing to know how it works. This is perfect for complex systems like a Reporting Tool where every report has a different generate() logic but shares a saveToDisk() method.
Example:
abstract class Vehicle {
abstract startEngine(): void; // Subclasses must define this
move(): void {
console.log("The vehicle is rolling down the road.");
}
}
class Car extends Vehicle {
startEngine(): void {
console.log("Ignition on: VRRRUM!");
}
}
class ElectricScooter extends Vehicle {
startEngine(): void {
console.log("System on: (Silent hum)");
}
}
const myCar = new Car();
myCar.startEngine(); // Custom logic
myCar.move(); // Shared logic
abstract.
Abstract Class with Constructor
Even though you can't create an instance of an abstract class, it can still have a constructor. This constructor is used to initialize properties that are shared by all subclasses. Subclasses call this using the super() keyword.
Example:
abstract class Shape {
// Shorthand for: this.color = color
constructor(public color: string) {}
abstract getArea(): number;
}
class Circle extends Shape {
constructor(color: string, public radius: number) {
super(color); // Passing color up to the Shape constructor
}
getArea(): number {
return Math.PI * Math.pow(this.radius, 2);
}
}
const greenCircle = new Circle("green", 10);
console.log(greenCircle.color); // Output: green
console.log(greenCircle.getArea()); // Output: 314.15...
In this logic, the Shape class handles the color, while the Circle class focuses purely on its own geometry calculations. This separation of concerns makes your code much cleaner.
Abstract Classes and Interfaces
A common question is: "When should I use an Interface versus an Abstract Class?" Use an Interface when you only need to define a shape (contracts). Use an Abstract Class when you want to define a shape and provide some shared, reusable code.
Interestingly, an abstract class can implement an interface. This is useful when you want to satisfy part of an interface but leave the rest for specific subclasses.
Example:
interface Movable {
speed: number;
move(): void;
}
abstract class Vehicle implements Movable {
constructor(public speed: number) {}
// We leave move() abstract for subclasses to define
abstract move(): void;
stopEngine(): void {
console.log("Engine stopped.");
}
}
class Bike extends Vehicle {
move(): void {
console.log(`Pedaling at ${this.speed} mph.`);
}
}
const myBike = new Bike(15);
myBike.move(); // Output: Pedaling at 15 mph.
Summary
Abstract classes in TypeScript are a foundational tool for Object-Oriented Programming (OOP). They provide a middle ground between the total flexibility of a regular class and the strict contract-only nature of an interface. Key takeaways include:
- Contract Enforcement:
abstractmethods ensure subclasses don't forget vital functionality. - Code Reuse: Non-abstract methods allow you to write logic once and use it in dozens of subclasses.
- Control: They prevent the accidental creation of "generic" objects that aren't fully formed (like a generic "Animal" or "Vehicle").
- Hierarchy: They help organize your code into logical families of objects.
By mastering abstract classes, you can build more robust, scalable, and predictable TypeScript applications.