Skip to Content
Course content

39: Classes and Access Modifiers

Click on the "Edit" button in the top corner of the screen to edit your slide content.

A few years ago, I was reviewing a PR for a junior dev who was building a payment processing module. They had a PaymentSession class with a property called transactionAmount. Because they didn't specify an access modifier, it defaulted to public. Later in the codebase, a UI component—which should have only been displaying the amount—had a bug that accidentally reassigned that value to zero during a state update. The code compiled perfectly, but the actual payment processed for $0.00. It was a classic case of "too much visibility." If that property had been private and only modifiable via a specific method, the compiler would have caught the bug instantly.

Guarding Your Data with Public, Private, and Protected

In TypeScript, classes aren't just blueprints for objects; they are tools for encapsulation. By default, every member of a class is public, meaning any part of your app can reach in and touch it. As you saw in my payment example, this is often a recipe for disaster. That's where access modifiers come in.

When you mark a property as private, it's only accessible within the class it was defined in. If you try to access a private member from an instance outside the class, TypeScript will throw a compile-time error. Now, you might wonder, "What if I want a child class to be able to see the property, but still keep it hidden from the rest of the world?" That's exactly why we have protected. It's a middle ground: it keeps the member private to the outside world but allows subclasses to inherit and use it.

class BankAccount {
  public accountHolder: string;
  private balance: number;
  protected accountType: string;

  constructor(holder: string, initialBalance: number, type: string) {
    this.accountHolder = holder;
    this.balance = initialBalance;
    this.accountType = type;
  }

  public getBalance() {
    return `Your balance is $${this.balance}`;
  }
}

class SavingsAccount extends BankAccount {
  public getAccountInfo() {
    // This works because accountType is protected
    return `This is a ${this.accountType} account for ${this.accountHolder}`;
  }
}

const myAcc = new BankAccount("Alice", 1000, "Checking");
console.log(myAcc.accountHolder); // OK
// console.log(myAcc.balance);    // Error: Property 'balance' is private

Cutting the Boilerplate with Parameter Properties

If you've written a lot of classes, you've probably noticed a tedious pattern: you declare a property, then you pass it into the constructor, and then you assign it using this.property = property. It's a lot of typing for very little value. TypeScript gives us a shorthand called "Parameter Properties" that I use in almost every project.

By adding an access modifier (like private or public) directly to the constructor argument, TypeScript automatically declares the property and assigns the value for you. It's a massive quality-of-life improvement.

// The long way
class User {
  private username: string;
  constructor(username: string) {
    this.username = username;
  }
}

// The TypeScript way (does exactly the same thing)
class UserShort {
  constructor(private username: string) {}
}

Preventing Accidental Overwrites with Readonly

Sometimes a property needs to be public—you want the rest of the app to be able to read it—but you don't want anyone to change it after the object has been created. This is where the readonly modifier shines. It's not an access modifier in the sense of visibility, but it's a powerful constraint on mutability.

I typically use readonly for IDs, configuration settings, or any "source of truth" that should be set once at instantiation and never touched again. If you try to reassign a readonly property, TypeScript will stop you dead in your tracks.

class Employee {
  constructor(public readonly employeeId: number, public name: string) {}
}

const emp = new Employee(12345, "Bob");
console.log(emp.employeeId); // OK
// emp.employeeId = 67890;   // Error: Cannot assign to 'employeeId' because it is a read-only property.



📋 Practical Task

Exercise: Secure Vault Management System

You need to build a Vault class that mimics a secure storage system. The requirements are as follows:

  • The vault must have a readonly property called vaultId (a string) that is set during initialization.
  • The vault must have a private property called secretCode (a string) that cannot be accessed from outside the class.
  • The vault should have a protected property called securityLevel (a number) that can be accessed by subclasses.
  • Implement a public method unlock(inputCode: string): boolean. This method should return true if the inputCode matches the secretCode, and false otherwise.
  • Create a subclass called HighSecurityVault that extends Vault. This subclass should have a method called getSecurityReport() that returns a string mentioning the securityLevel.

Test your implementation: Create an instance of HighSecurityVault, attempt to change the vaultId (it should fail), attempt to access the secretCode directly (it should fail), and verify that unlock() and getSecurityReport() work as expected.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.