Skip to content
Laviyx Learn

Try

TypeScript course menu

TypeScript Classes: Typed Properties, public, private, protected, readonly and Parameter Properties

Learn classes in TypeScript: typed properties and constructors, access modifiers public, private and protected, readonly, parameter properties, TypeScript private versus JavaScript #private fields, getters and setters, static members, and classes as types.

Intermediate12 min readUpdated
On this page

TypeScript adds types and access control to JavaScript classes. Everything you know about JavaScript classes still applies; this lesson covers what TypeScript adds.

Typed properties#

Class properties are declared with their types in the class body:

TypeScript
class Product {
  name: string;
  price: number;
  tags: string[] = [];

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

  withTax(rate = 0.18): number {
    return Math.round(this.price * (1 + rate));
  }
}

const pen = new Product("Pen", 20);
pen.tags.push("stationery");
console.log(pen.name, pen.withTax(), pen.tags);
Output
Pen 24 [ 'stationery' ]

In strict mode, every property must be initialised, either where it is declared or in the constructor:

TypeScript
class User {
  name: string;
  email: string;
  constructor(name: string) {
    this.name = name;
  }
}
Compiler error
error TS2564: Property 'email' has no initializer and is not definitely assigned in the constructor.

Access modifiers#

ModifierAccessible from
public (default)Anywhere
privateOnly inside this class
protectedInside this class and its subclasses
TypeScript
class BankAccount {
  private balance = 0;

  deposit(amount: number): void {
    this.balance += amount;
  }

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

const account = new BankAccount();
account.deposit(500);
account.balance = 1000000;
Compiler error
error TS2341: Property 'balance' is private and only accessible within class 'BankAccount'.

protected members can be used in subclasses:

TypeScript
class Employee {
  protected baseSalary: number;
  constructor(public name: string, baseSalary: number) {
    this.baseSalary = baseSalary;
  }
  salary(): number {
    return this.baseSalary;
  }
}

class Manager extends Employee {
  salary(): number {
    return this.baseSalary * 1.2;   // allowed: protected
  }
}

console.log(new Manager("Asha", 100000).salary());
Output
120000

readonly#

readonly properties can be set in the declaration or the constructor, and never again:

TypeScript
class Order {
  readonly id: number;
  constructor(id: number) {
    this.id = id;
  }
}

const order = new Order(1042);
order.id = 1;
Compiler error
error TS2540: Cannot assign to 'id' because it is a read-only property.

Parameter properties#

Writing a property declaration, a constructor parameter and an assignment for each field is repetitive. Adding a modifier (public, private, protected or readonly) to a constructor parameter does all three at once:

TypeScript
class Student {
  constructor(
    public readonly id: number,
    public name: string,
    private marks: number[] = [],
  ) {}

  addMark(mark: number): void {
    this.marks.push(mark);
  }

  get average(): number {
    return this.marks.length ? this.marks.reduce((a, b) => a + b, 0) / this.marks.length : 0;
  }
}

const s = new Student(1, "Ravi");
s.addMark(80);
s.addMark(90);
console.log(s.id, s.name, s.average);
Output
1 Ravi 85

Parameter properties are a TypeScript-only feature that generates code, so, like enums, they are not supported by tools that only strip types.

private versus #private#

TypeScript's private is only checked at compile time; at runtime the property is an ordinary property that JavaScript code (or a type assertion) could still read. JavaScript's own #private fields are enforced at runtime:

TypeScript
class A {
  private secret = "compile-time only";
}
class B {
  #secret = "runtime private";
  reveal() {
    return this.#secret;
  }
}

console.log(Object.keys(new A()));
console.log(Object.keys(new B()), new B().reveal());
Output
[ 'secret' ]
[] runtime private

Both are fine to use; #private is the stronger guarantee.

Getters, setters and static members#

These work as in JavaScript, with types:

TypeScript
class Temperature {
  static readonly ABSOLUTE_ZERO = -273.15;
  #celsius = 0;

  get celsius(): number {
    return this.#celsius;
  }
  set celsius(value: number) {
    if (value < Temperature.ABSOLUTE_ZERO) throw new RangeError("Too cold");
    this.#celsius = value;
  }
  get fahrenheit(): number {
    return (this.#celsius * 9) / 5 + 32;
  }
}

const t = new Temperature();
t.celsius = 25;
console.log(t.fahrenheit, Temperature.ABSOLUTE_ZERO);
Output
77 -273.15

A class is also a type#

Declaring a class creates both a runtime constructor and a type with the same name, describing its instances:

TypeScript
class Point {
  constructor(public x: number, public y: number) {}
}

function distance(a: Point, b: Point): number {
  return Math.hypot(a.x - b.x, a.y - b.y);
}

console.log(distance(new Point(0, 0), new Point(3, 4)));
console.log(distance({ x: 0, y: 0 }, { x: 6, y: 8 }));   // plain objects with the same shape work too
Output
5
10

Because TypeScript is structural, any object with matching public members is accepted, even if it was not created with new Point.

Practice#

Practice exercise

Create a Library class with a private books array of { title: string; available: boolean }, a method add(title: string), a method borrow(title: string): boolean that marks a book as unavailable (returning false if it is missing or already borrowed), and a getter availableCount. Test it.

Show solution
TypeScript
type Book = { title: string; available: boolean };

class Library {
  private books: Book[] = [];

  add(title: string): void {
    this.books.push({ title, available: true });
  }

  borrow(title: string): boolean {
    const book = this.books.find((b) => b.title === title);
    if (!book || !book.available) return false;
    book.available = false;
    return true;
  }

  get availableCount(): number {
    return this.books.filter((b) => b.available).length;
  }
}

const library = new Library();
library.add("Learning TS");
library.add("Web Basics");
console.log(library.borrow("Learning TS"), library.borrow("Learning TS"), library.borrow("Missing"));
console.log(library.availableCount);
Output
true false false
1

Key takeaway#

Finished this lesson?