intermediate Step 9 of 18

Classes

TypeScript Programming

Classes in TypeScript

TypeScript classes build on the ES6 class syntax by adding type annotations, access modifiers, abstract classes, and parameter properties. While JavaScript classes provide the runtime behavior, TypeScript's additions enable you to enforce encapsulation at compile time — restricting which parts of a class are accessible from outside and ensuring that subclasses implement required methods. These features make TypeScript classes suitable for building large, maintainable object-oriented systems where clear contracts between components are essential.

Typed Properties and Constructors

Class properties must be declared with their types before they can be used. TypeScript requires that all properties are either initialized in the declaration or assigned in the constructor when strictPropertyInitialization is enabled.

class User {
  id: number;
  name: string;
  email: string;
  createdAt: Date;

  constructor(id: number, name: string, email: string) {
    this.id = id;
    this.name = name;
    this.email = email;
    this.createdAt = new Date();
  }

  getDisplayName(): string {
    return `${this.name} (${this.email})`;
  }
}

const user = new User(1, "Alice", "alice@example.com");
console.log(user.getDisplayName());

Access Modifiers

TypeScript provides three access modifiers: public (default, accessible everywhere), private (accessible only within the class), and protected (accessible within the class and its subclasses). These modifiers exist only at compile time and are not enforced at runtime.

class BankAccount {
  public owner: string;
  private balance: number;
  protected accountNumber: string;

  constructor(owner: string, initialBalance: number) {
    this.owner = owner;
    this.balance = initialBalance;
    this.accountNumber = this.generateAccountNumber();
  }

  // Public method — accessible from anywhere
  public deposit(amount: number): void {
    if (amount <= 0) throw new Error("Amount must be positive");
    this.balance += amount;
  }

  public getBalance(): number {
    return this.balance;
  }

  // Private method — only accessible within this class
  private generateAccountNumber(): string {
    return `ACC-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
  }
}

const account = new BankAccount("Alice", 1000);
account.deposit(500);
console.log(account.getBalance()); // 1500
// account.balance;        // Error: Property 'balance' is private
// account.accountNumber;  // Error: Property 'accountNumber' is protected

Parameter Properties

TypeScript offers a shorthand for declaring and initializing properties directly in the constructor parameter list by prefixing them with an access modifier.

class Product {
  constructor(
    public readonly id: number,
    public name: string,
    public price: number,
    private stock: number = 0
  ) {}

  isInStock(): boolean {
    return this.stock > 0;
  }

  sell(quantity: number): void {
    if (quantity > this.stock) {
      throw new Error("Insufficient stock");
    }
    this.stock -= quantity;
  }
}

const item = new Product(1, "Widget", 9.99, 100);
console.log(item.name);       // "Widget"
console.log(item.isInStock()); // true

Abstract Classes

Abstract classes cannot be instantiated directly. They serve as base classes that define common structure and behavior while requiring subclasses to implement specific abstract methods. This enforces that all subclasses provide certain functionality.

abstract class Shape {
  constructor(public color: string) {}

  // Abstract methods must be implemented by subclasses
  abstract area(): number;
  abstract perimeter(): number;

  // Concrete method shared by all subclasses
  describe(): string {
    return `${this.color} shape: area=${this.area().toFixed(2)}, perimeter=${this.perimeter().toFixed(2)}`;
  }
}

class Circle extends Shape {
  constructor(color: string, public radius: number) {
    super(color);
  }

  area(): number {
    return Math.PI * this.radius ** 2;
  }

  perimeter(): number {
    return 2 * Math.PI * this.radius;
  }
}

class Rectangle extends Shape {
  constructor(color: string, public width: number, public height: number) {
    super(color);
  }

  area(): number {
    return this.width * this.height;
  }

  perimeter(): number {
    return 2 * (this.width + this.height);
  }
}

// const s = new Shape("red"); // Error: Cannot create an instance of an abstract class
const circle = new Circle("red", 5);
const rect = new Rectangle("blue", 4, 6);
console.log(circle.describe()); // "red shape: area=78.54, perimeter=31.42"
console.log(rect.describe());   // "blue shape: area=24.00, perimeter=20.00"
Tip: Use abstract classes when you want to share implementation code between related classes. Use interfaces when you only need to define a contract without shared behavior.

Key Takeaways

  • TypeScript class properties must be declared with types and initialized before use.
  • Access modifiers (public, private, protected) enforce encapsulation at compile time.
  • Parameter properties in constructors reduce boilerplate by combining declaration and initialization.
  • Abstract classes define contracts that subclasses must fulfill while sharing common behavior.
  • Use readonly on properties that should not change after construction.