Classes & Object-Oriented TypeScript
๐๏ธ Classes & Object-Oriented TypeScript
๐๏ธ 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!
class Dog { }โ defines the blueprintname: string; age: number;โ properties (with types!)constructor()โ runs when you create a new instancebark()โ a method (function inside the class)thisโ refers to the current instancenew Dog()โ creates an instance
๐ Access Modifiers
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
You can't directly set balance to an invalid amount. You must use deposit/withdraw, which validate the transaction.
๐จโ๐ฉโ๐งโ๐ฆ Inheritance
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
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().
Challenge 2: Access Modifiers
Create a BankAccount class with:
- Private
balanceproperty (starts at 0) - Public
deposit(amount)method - Public
getBalance()method - Ensure balance can't go negative
Goal: Demonstrate why private properties protect data.
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.
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.