Understanding TypeScript Abstract Classes: A Complete Guide

If you’re diving into TypeScript and object-oriented programming, understanding abstract classes is crucial for writing flexible, maintainable code. Abstract classes serve as blueprints for other classes while containing some implementation details themselves. Let’s explore how they work and when to use them.

Abstract classes bridge the gap between interfaces and concrete classes, offering a powerful way to share code between related classes while enforcing certain implementation requirements.

Table of Contents

What Are Abstract Classes?

An abstract class is a class that can’t be instantiated directly but can serve as a base class for other classes. Think of it as a partially completed blueprint – it provides some implementation details but leaves others to be filled in by the classes that extend it.

Why Use Abstract Classes?

Abstract classes are particularly useful when you want to:

  • Share code between several related classes
  • Enforce certain methods to be implemented by child classes
  • Provide default implementations while keeping some methods abstract

Syntax and Basic Usage

Here’s how to create an abstract class in TypeScript:

abstract class Animal {
  name: string;

  constructor(name: string) {
    this.name = name;
  }

  // Concrete method with implementation
  makeSound(): void {
    console.log('Some generic sound');
  }

  // Abstract method - must be implemented by derived classes
  abstract move(): void;
}
Code language: JavaScript (javascript)

Creating Concrete Classes

To use an abstract class, you need to create a concrete class that extends it:

class Dog extends Animal {
  // Must implement the abstract move method
  move(): void {
    console.log(`${this.name} is running on four legs`);
  }

  // Can override concrete methods
  makeSound(): void {
    console.log('Woof! Woof!');
  }
}
Code language: JavaScript (javascript)

Abstract Methods vs Concrete Methods

In abstract classes, you can have two types of methods:

  1. Abstract Methods:
  • Declared without implementation
  • Must be implemented by derived classes
  • Use the ‘abstract’ keyword
  1. Concrete Methods:
  • Have a default implementation
  • Can be overridden by derived classes
  • Optional to override

Best Practices

When to Use Abstract Classes

  1. Base Class Pattern
abstract class HttpService {
  abstract baseUrl: string;

  protected async get(endpoint: string): Promise<any> {
    const response = await fetch(`${this.baseUrl}/${endpoint}`);
    return response.json();
  }
}

class UserService extends HttpService {
  baseUrl = 'https://api.example.com/users';
}
Code language: JavaScript (javascript)
  1. Template Method Pattern
abstract class DataProcessor {
  process(): void {
    this.validate();
    this.transform();
    this.save();
  }

  protected abstract validate(): void;
  protected abstract transform(): void;
  protected abstract save(): void;
}
Code language: JavaScript (javascript)

Common Pitfalls and Solutions

Pitfall 1: Trying to Instantiate Abstract Classes

Incorrect:

const animal = new Animal('Generic'); // Error!
Code language: JavaScript (javascript)

Correct:

const dog = new Dog('Rex'); // Works fine
Code language: JavaScript (javascript)

Pitfall 2: Forgetting to Implement Abstract Methods

TypeScript will show an error if you forget to implement any abstract methods in your concrete class. Always ensure all abstract methods are implemented.

Real-World Examples

Form Validation Example

abstract class FormValidator {
  abstract validate(): boolean;

  protected isEmpty(value: string): boolean {
    return value.trim().length === 0;
  }

  protected isEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

class LoginFormValidator extends FormValidator {
  constructor(private email: string, private password: string) {
    super();
  }

  validate(): boolean {
    return !this.isEmpty(this.email) && 
           this.isEmail(this.email) && 
           !this.isEmpty(this.password);
  }
}
Code language: JavaScript (javascript)

When to Use Abstract Classes vs Interfaces

Use abstract classes when:

  • You need to share code between several related classes
  • You want to provide default implementations for some methods
  • You need to declare private or protected members

Use interfaces when:

  • You only need to define a contract for object structure
  • You want to enable multiple inheritance
  • You don’t need any implementation details

Conclusion

Abstract classes are a powerful feature in TypeScript that enable you to create flexible, reusable code while maintaining type safety and enforcing implementation requirements. They’re particularly useful in large applications where you need to share code between related classes while ensuring certain methods are implemented.

Remember that while interfaces define what an object should look like, abstract classes can provide both structure and implementation details. Choose the right tool based on your specific needs – if you need to share implementation, go with abstract classes; if you only need to define a contract, use interfaces.

For more TypeScript concepts, check out our guide on TypeScript Interfaces or explore TypeScript Type Assertions to deepen your understanding of TypeScript’s type system.

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Share via
Copy link
Powered by Social Snap