Classes
TypeScript classes are JavaScript classes with types added: typed properties, typed constructor parameters and methods, and access modifiers that control which members can be reached from outside the class.
A typed class
class BankAccount { private balance: number; public readonly owner: string; constructor(owner: string, startingBalance: number) { this.owner = owner; this.balance = startingBalance; } deposit(amount: number): void { this.balance += amount; console.log(`Deposited ${amount}. New balance: ${this.balance}`); } } const acc = new BankAccount("Priya", 100); acc.deposit(50); console.log(acc.balance);
error TS2341: Property 'balance' is private and only accessible within class 'BankAccount'.
Each property is declared in the class body with its type. With strict mode on, every property must be initialised — either where it's declared or in the constructor — or TypeScript reports that it might be undefined.
Access modifiers
| Modifier | Accessible from |
|---|---|
public (default) | Anywhere |
protected | The class and its subclasses (lesson 18) |
private | Only inside the class itself |
The last line of the example above fails because it reaches into a private property from outside. Remove it and the program runs normally.
readonly properties
class Invoice { readonly number: string; constructor(n: string) { this.number = n; } } const inv = new Invoice("INV-001"); inv.number = "INV-002";
error TS2540: Cannot assign to 'number' because it is a read-only property.
A readonly property can be assigned in its declaration or in the constructor, and never again.
Parameter properties
Declaring a property, accepting it as a constructor parameter and assigning it is so common that TypeScript has a shorthand. Put a modifier in front of a constructor parameter and it becomes a property automatically:
class Point { constructor(public readonly x: number, public readonly y: number) {} distanceTo(other: Point): number { return Math.hypot(other.x - this.x, other.y - this.y); } } const a = new Point(0, 0); const b = new Point(3, 4); console.log(a.distanceTo(b));
5
That one-line constructor declares x and y, and assigns them. Note that parameter properties are one of the few TypeScript features that generate code, so tools that only strip types (like Node's built-in type stripping) don't support them.
Implementing an interface
interface Shape { area(): number; describe(): string; } class Rectangle implements Shape { constructor(private width: number, private height: number) {} area(): number { return this.width * this.height; } describe() { return `${this.width}×${this.height} rectangle`; } } const rect = new Rectangle(4, 5); console.log(rect.describe(), "=", rect.area());
4×5 rectangle = 20
implements Shape makes the compiler check that Rectangle provides everything Shape requires, with compatible types. A class can implement several interfaces: class A implements B, C.
A class is also a type
Declaring a class creates two things: a constructor function (a runtime value) and a type describing its instances. You can use the class name in annotations:
class Tag { constructor(public name: string) {} } function render(tags: Tag[]): string { return tags.map(t => `#${t.name}`).join(" "); } console.log(render([new Tag("ts"), new Tag("web")]));
#ts #web
TypeScript private vs JavaScript #private
private is checked only by the compiler. After compilation, the property is an ordinary property that any JavaScript code can read. JavaScript's own private fields, written with #, are enforced at runtime:
class Wallet { #coins = 0; add(n: number) { this.#coins += n; return this.#coins; } } const w = new Wallet(); console.log(w.add(3)); console.log(Object.keys(w));
3 []
#coins doesn't even appear in Object.keys. Use # when real runtime privacy matters (for example, in a library), and private when compile-time checking is enough.