TypeScript Survival Guide

Favorite typescript videos

013 — Access Modifiers (public, private, readonly, shorthand)

Full companion narrative for TypeScript Tutorial — Classes: public, private, readonly: who may read or change each Invoice property, and the constructor parameter-property shorthand

Video: 013 · TypeScript Tutorial - Classes: public, private, readonly · watch · ~6:05 · Net Ninja TypeScript tutorial series, module 05 (objects and interfaces)

Source: this is a written companion to the video. Part of the Favorite TypeScript Videos curated path, module 05 (objects and interfaces). The second classes lesson: the Invoice class from 012 already works — now the question is who is allowed to read or change each property, and where.

the Invoice class with everything open

The lesson opens with the Invoice class as built previously: three properties (client: string, details: string, amount: number), a constructor that assigns them, and a format() method returning a sentence. Two instances (invOne for mario, invTwo for luigi) sit in an invoices array, and a forEach loop logs each invoice’s client, details, amount, and format() result to the console.

class Invoice {
	client: string;
	details: string;
	amount: number;

	constructor(client: string, details: string, amount: number) {
		this.client = client;
		this.details = details;
		this.amount = amount;
	}

	format(): string {
		return `${this.client} owes £${this.amount} for ${this.details}`;
	}
}

const invOne = new Invoice("mario", "work on the mario website", 250);
const invTwo = new Invoice("luigi", "work on the luigi website", 300);
const invoices: Invoice[] = [invOne, invTwo];

invoices.forEach((inv) => {
	console.log(inv.client, inv.details, inv.amount, inv.format());
});
// console: mario / work on the mario website / 250 / "mario owes £250 for ..."
// console: luigi / work on the luigi website / 300 / "luigi owes £300 for ..."

editor showing the Invoice class, both instances, and the forEach log loop

Finance Logger page with mario 250 / luigi 300 listed and console output

The key observation: every property can be both read and reassigned from outside the class after construction. That is the default, and the video makes it explicit before changing anything.

public, the default

Every property without a modifier is public: readable and writable both inside the class and on instances outside it. Writing public in front changes nothing — it just states the default out loud.

class Invoice {
	public client: string; // identical to `client: string;` — public by default
	// ...
}

invOne.client = "yoshi"; // allowed: public means anyone can read or change it
console.log(invOne.client); // allowed

Teach “no modifier = public” as the baseline, then present private and readonly as restrictions on top of it.

private fields, inside-only access

Marking a property private locks it to the class body. Code inside the class (e.g. this.details in format()) can still read and write it; code outside, on an instance (inv.details), cannot — the compiler rejects it.

The video demonstrates this by making details private: the console.log(inv.details) line in the loop immediately errors, so the presenter deletes that direct access. The format() method keeps working because it touches this.details from inside the class, and the details still appear in the console via the formatted sentence.

class Invoice {
	client: string;
	private details: string; // inside-only from here on
	amount: number;
	// constructor + format() as before
}

invoices.forEach((inv) => {
	// console.log(inv.details); // ERROR: Property 'details' is private
	console.log(inv.client, inv.amount, inv.format()); // fine — format() reads details internally
});

editor with private details; direct details log removed, format() still logging

browser console showing client, amount, and format() output after the private change

private is about encapsulation — hide the raw field, expose it only through a method you control.

Finance Logger page and console during the readonly discussion

readonly fields, readable but frozen

readonly splits the difference: the property can be read both inside and outside the class, but cannot be reassigned anywhere — not on the instance, not even with this. inside the class body. (The initial assignment in the constructor is the one exception; after that it is frozen.)

The video demonstrates both failure modes: inv.client = "..." outside the class errors, and this.client = "..." inside the class errors too.

class Invoice {
	readonly client: string;
	private details: string;
	public amount: number;
	// ...
}

// invOne.client = "yoshi";      // ERROR: Cannot assign to 'client' because it is a read-only property.
// this.client = "yoshi";        // ERROR inside the class as well — same rule.
console.log(invOne.client); // fine — readonly is still readable everywhere.

editor at the readonly discussion; loop logging client, amount, and format()

Summary table the video builds:

modifierread outsidechange outsideread/write inside
public (default)yesyesyes
privatenonoyes
readonlyyesnoread-only (no reassignment, even inside)

modifiers on parameters replace the boilerplate

The closing lesson: when every property uses an access modifier, the explicit field declarations plus manual this.x = x assignments collapse into the constructor signature. Prefixing a constructor parameter with public, private, or readonly tells TypeScript to create a property of that name and auto-assign the passed value.

Requirements stated in the video: the shorthand only works with a modifier present — bare parameters without one do not become properties.

// longhand (commented out in the video) vs shorthand (kept):
class Invoice {
	// readonly client: string;
	// private details: string;
	// public amount: number;
	// constructor(client: string, details: string, amount: number) {
	//   this.client = client;
	//   this.details = details;
	//   this.amount = amount;
	// }

	constructor(
		readonly client: string,
		private details: string,
		public amount: number,
	) {}

	format(): string {
		return `${this.client} owes £${this.amount} for ${this.details}`;
	}
}
// behavior unchanged: mario 250 / luigi 300 still log exactly as before.

browser and console confirming identical output after the shorthand refactor

This shorthand is the idiom learners will meet in real codebases — the preferred form, with the longhand shown once so the auto-assignment is not magic.

The outro splits the class into its own file — 014 — Modules.

Prev: 012 — Classes · Next: 014 — Modules

Transcript notes: loop variable inv (not “in”), forEach, property as a named value on the class, instance as one new Invoice(...) object, “outside the class” meaning instance code vs this. body code. Each change is demoed by breaking it on purpose, reading the compiler error, and reverting. Modules and interfaces follow next.