Access Modifiers

Difficulty: Intermediate

Access modifiers in TypeScript control the visibility and mutability of class members. They are a core part of encapsulation, one of the fundamental principles of object-oriented programming. By restricting access to certain properties and methods, you create a clear boundary between a class's internal implementation and its public API.

The `public` modifier is the default in TypeScript - every member without an explicit modifier is public. Public members can be accessed from anywhere: inside the class, in subclasses, and from external code. The `private` modifier restricts access to the class itself. Private members cannot be accessed from outside the class or from subclasses. TypeScript also supports the JavaScript native `#` private field syntax, which provides true runtime privacy.

The `protected` modifier sits between public and private. Protected members are accessible within the class and in subclasses, but not from external code. This is useful when you want to share implementation details with child classes while still hiding them from the rest of the application.

The `readonly` modifier prevents a property from being reassigned after initialization. You can set a readonly property in the constructor or at the declaration site, but any subsequent assignment will cause a compile error. Combining `readonly` with other access modifiers (e.g., `private readonly`) gives you fine-grained control over who can read a value and ensures it never changes after construction.

Code examples

Public, Private, and Protected

class Employee {
  public name: string;
  private salary: number;
  protected department: string;

  constructor(name: string, salary: number, department: string) {
    this.name = name;
    this.salary = salary;
    this.department = department;
  }

  public describe(): string {
    return `${this.name} works in ${this.department}`;
  }

  private getSalary(): number {
    return this.salary;
  }
}

class Manager extends Employee {
  showDepartment(): string {
    // Can access protected member
    return `Managing ${this.department}`;
  }
}

const emp = new Employee("Bob", 50000, "Engineering");
console.log(emp.name);
console.log(emp.describe());
// emp.salary; // Error: private
// emp.department; // Error: protected

const mgr = new Manager("Carol", 80000, "Design");
console.log(mgr.showDepartment());

The `name` is public and accessible everywhere, `salary` is private and only accessible inside Employee, and `department` is protected so Manager can access it but external code cannot.

Readonly Properties

class Point {
  readonly x: number;
  readonly y: number;

  constructor(x: number, y: number) {
    this.x = x;
    this.y = y;
  }

  toString(): string {
    return `(${this.x}, ${this.y})`;
  }
}

const p = new Point(10, 20);
console.log(p.toString());
console.log(p.x);
// p.x = 99; // Error: Cannot assign to 'x' because it is a read-only property

The readonly modifier ensures x and y can only be set in the constructor. Any attempt to reassign them later causes a compile-time error.

Combining Modifiers

class AppConfig {
  private readonly apiUrl: string;
  private readonly maxRetries: number;

  constructor(apiUrl: string, maxRetries: number = 3) {
    this.apiUrl = apiUrl;
    this.maxRetries = maxRetries;
  }

  getApiUrl(): string {
    return this.apiUrl;
  }

  getMaxRetries(): number {
    return this.maxRetries;
  }
}

const config = new AppConfig("https://api.example.com", 5);
console.log(config.getApiUrl());
console.log(config.getMaxRetries());

Using `private readonly` ensures the values are neither accessible from outside nor changeable after construction, a common pattern for configuration objects.

Key points

Concepts covered

public, private, protected, readonly, encapsulation