Access Modifiers & Parameter Properties
Control member visibility, and declare-plus-assign fields right in the constructor signature.
TypeScript offers three visibility keywords plus JavaScript's own private fields:
public— the default; visible everywhere.protected— visible in the class and its subclasses.private— visible only inside the declaring class (enforced at compile time).#field— JavaScript's runtime hard-private, truly inaccessible from outside.
Parameter properties
Put a modifier in front of a constructor parameter and TypeScript declares the field and assigns it for you — no more this.x = x boilerplate.
class Point {
constructor(
public readonly x: number,
public readonly y: number,
) {}
}That five-line class fully declares two readonly public fields. Modifiers and readonly combine freely, so private readonly is a common, expressive choice.
Example
When to use it
- A service class uses private constructor parameters to inject dependencies without exposing them to subclasses.
- A domain entity uses public readonly constructor parameters to create an immutable value object concisely.
- A base class marks a helper as protected so only its subclasses can call it, keeping it out of the public API.
More examples
Parameter properties shorthand
Constructor parameter properties declare, assign, and apply access modifiers in one step without property declarations.
class User {
constructor(
public readonly id: number,
public name: string,
private email: string,
) {}
getEmail(): string { return this.email; }
}
const u = new User(1, 'Alice', '[email protected]');
console.log(u.id, u.name);
// u.email; // Error: privateProtected access in subclass
Protected members are accessible in subclasses but not from external code, a middle ground between private and public.
class Base {
constructor(protected config: string) {}
protected validate(): boolean {
return this.config.length > 0;
}
}
class Derived extends Base {
run(): void {
if (this.validate()) console.log('Config:', this.config);
}
}Private field vs # private
Contrasts TypeScript's private keyword (erased at runtime) with the # native private field (truly private in JS).
class Counter {
private tsPrivate: number = 0; // TypeScript-only
#jsPrivate: number = 0; // native JS private field
increment(): void {
this.tsPrivate++;
this.#jsPrivate++;
}
get values(): [number, number] {
return [this.tsPrivate, this.#jsPrivate];
}
}
Discussion