️ Classes & Object-Oriented TypeScript

TypeScript Fundamentals — Classes & Object-Oriented TypeScript
TypeScript Fundamentals
Course 1 · Chapter 5 · Classes & Object-Oriented TypeScript

🏗️ Classes & Object-Oriented TypeScript

Classes are the core of object-oriented programming. They let you create blueprints for objects, define behavior with methods, enforce constraints with access modifiers, and share functionality through inheritance. TypeScript adds type safety to JavaScript's class system.

🏛️ Basic Class Structure

Your First Class

class Dog { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } bark(): void { console.log(`${this.name} says woof!`); } } // Create an instance const myDog = new Dog("Buddy", 3); myDog.bark(); // Output: Buddy says woof!
Key parts:
  • class Dog { } — defines the blueprint
  • name: string; age: number; — properties (with types!)
  • constructor() — runs when you create a new instance
  • bark() — a method (function inside the class)
  • this — refers to the current instance
  • new Dog() — creates an instance

🔐 Access Modifiers

Access modifiers control who can access properties and methods. TypeScript provides three: public, private, and protected.

public

Default. Anyone can access from anywhere.

class User { public name: string; } const user = new User(); user.name = "Alice"; // ✅ OK

private

Only accessible inside the class.

class User { private password: string; } const user = new User(); console.log(user.password); // ❌ Error!

protected

Accessible inside the class and subclasses.

class Animal { protected health: number; } class Dog extends Animal { heal() { this.health++; } // ✅ OK }

readonly

Can't be changed after initialization.

class User { readonly id: number; constructor(id: number) { this.id = id; // ✅ OK in constructor } } const user = new User(1); user.id = 2; // ❌ Error! readonly

Why Access Modifiers Matter

Consider a bank account:

class BankAccount { private balance: number = 0; deposit(amount: number): void { if (amount > 0) this.balance += amount; } withdraw(amount: number): boolean { if (amount <= this.balance) { this.balance -= amount; return true; } return false; } public getBalance(): number { return this.balance; } } const account = new BankAccount(); account.deposit(100); // ✅ OK - uses method console.log(account.getBalance()); // ✅ OK - public method account.balance = 1000000; // ❌ Error! balance is private
✅ Private balance enforces business logic:

You can't directly set balance to an invalid amount. You must use deposit/withdraw, which validate the transaction.

👨‍👩‍👧‍👦 Inheritance

Inheritance lets you create a class that extends another, inheriting its properties and methods while adding its own.
class Animal { name: string; constructor(name: string) { this.name = name; } move(): void { console.log(`${this.name} is moving`); } } class Dog extends Animal { bark(): void { console.log(`${this.name} says woof!`); } // Override the parent method move(): void { console.log(`${this.name} runs fast`); } } const dog = new Dog("Buddy"); dog.move(); // Output: Buddy runs fast (overridden method) dog.bark(); // Output: Buddy says woof!

super: Calling Parent Methods

Use super to call parent class methods:

class Dog extends Animal { breed: string; constructor(name: string, breed: string) { super(name); // Call parent constructor this.breed = breed; } move(): void { super.move(); // Call parent method console.log(` (as a ${this.breed})`); } }

🎭 Abstract Classes

An abstract class is a blueprint that can't be instantiated directly. Its purpose is to define methods that subclasses must implement. Think of it as a contract.
abstract class Shape { abstract getArea(): number; // Subclasses MUST implement public describe(): void { console.log(`This shape has area: ${this.getArea()}`); } } // ❌ Can't do this: const shape = new Shape(); // Error! Can't instantiate abstract class class Circle extends Shape { radius: number; constructor(radius: number) { super(); this.radius = radius; } getArea(): number { return Math.PI * this.radius ** 2; } } // ✅ This works: const circle = new Circle(5); circle.describe(); // Output: This shape has area: 78.539...

⚡ Constructor Parameter Shorthand

TypeScript lets you declare and initialize properties directly in the constructor:

// Before (verbose): class User { name: string; age: number; constructor(name: string, age: number) { this.name = name; this.age = age; } } // After (shorthand): class User { constructor( public name: string, public age: number ) {} } // Both are equivalent!

💻 Coding Challenges

Challenge 1: Create a Vehicle Class

Create a Vehicle class with:

  • Properties: brand, model, year (all public)
  • Constructor to initialize these
  • A method getInfo() that returns a description string

Goal: Create 2-3 vehicle instances and call getInfo().

→ Solution

Challenge 2: Access Modifiers

Create a BankAccount class with:

  • Private balance property (starts at 0)
  • Public deposit(amount) method
  • Public getBalance() method
  • Ensure balance can't go negative

Goal: Demonstrate why private properties protect data.

→ Solution

Challenge 3: Inheritance and Overriding

Create an Animal class with a speak() method. Then create Dog and Cat subclasses that override speak() with their own sounds.

  • Animal: speaks generically
  • Dog: says "Woof!"
  • Cat: says "Meow!"

Goal: Demonstrate polymorphism and method overriding.

→ Solution

💡 Real-World Tip: Use private by Default

Start by making properties private, then expose what you need through public methods. This is safer than making everything public and trying to restrict later. It forces you to think about your API and protects internal state from accidental changes.

🎯 What's Next

Classes are powerful, but sometimes you need more flexibility. Chapter 6 introduces Generics — the TypeScript way to write code that works with many types while staying type-safe.