Parameter Properties
Parameter properties are a TypeScript shorthand that lets you declare and initialize a class field directly from a constructor parameter. By adding an access modifier (public, private, protected) or readonly in front of a constructor parameter, you tell the compiler to create a field of the same name and assign the argument to it automatically. This removes the repetitive “declare, accept, assign” boilerplate that plain classes require. It is a TypeScript-only feature with no JavaScript equivalent, and it is especially common in dependency-injection frameworks like Angular and NestJS. This page covers the syntax, what it expands to, modifier combinations, and the rules and trade-offs.
The problem they solve
Without parameter properties you write the same field name three times — once to declare it, once as a parameter, and once to assign it. This is pure boilerplate.
class User {
private id: string;
public name: string;
constructor(id: string, name: string) {
this.id = id;
this.name = name;
}
}
Every field repeats: a declaration, a parameter, and an assignment line. For a class with several dependencies this triples in size for no added meaning.
The shorthand syntax
Add a modifier to the constructor parameter and TypeScript declares the field and assigns it for you. The class below is equivalent to the one above.
class User {
constructor(
private id: string,
public name: string,
) {}
}
const u = new User("u1", "Ada");
u.name; // ✅ "Ada" — field created automatically
u.id; // ❌ Error: Property 'id' is private
The modifier is what triggers the behavior. private id becomes a private field; public name becomes a public field. The constructor body is empty because the assignments are generated.
What it compiles to
It helps to see the emitted JavaScript. The modifiers are erased (they are types), but the assignments remain — proving these are real fields at runtime.
// TypeScript
class Point {
constructor(public x: number, public y: number) {}
}
// Emitted JavaScript
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
}
The compiler injects this.x = x and this.y = y at the top of the constructor. This is why a parameter property is indistinguishable from a manually declared field once compiled.
Combining with readonly
You can mix readonly with an access modifier, or use readonly alone (which implies public). This is the idiomatic way to declare immutable dependencies.
class HttpService {
constructor(
public readonly baseUrl: string,
private readonly timeout: number = 5000,
) {}
describe(): string {
return `${this.baseUrl} (${this.timeout}ms)`;
}
}
const s = new HttpService("https://api.dev");
s.baseUrl = "x"; // ❌ Error: Cannot assign to 'baseUrl' (read-only)
Note that timeout also has a default value — parameter properties support defaults and optionality just like normal parameters. readonly alone still creates a public, immutable field.
Modifier reference
Exactly one of the following must be present for the parameter to become a property. A bare parameter (no modifier) stays an ordinary local argument.
| Declaration | Resulting field |
|---|---|
public x: T | Public field x |
private x: T | Private field x |
protected x: T | Protected field x |
readonly x: T | Public, read-only field x |
private readonly x: T | Private, read-only field x |
x: T (no modifier) | No field — plain parameter |
class Mixed {
field?: string;
constructor(
public id: string, // parameter property
plain: string, // plain parameter — NOT a field
) {
this.field = plain; // assign manually if you need it
}
}
Parameter properties only work in a real
constructor. They cannot be used with#privatefields — the ECMAScript#namesyntax is not allowed in parameter position, so use a TypeScript modifier instead.
Best Practices
- Use parameter properties to eliminate constructor boilerplate, especially for injected dependencies.
- Mark injected services
private readonlyso they cannot be reassigned. - Keep the parameter list readable; put each property on its own line with a trailing comma.
- Remember a parameter without a modifier is just a local — add one to make it a field.
- Mix in defaults and optional markers (
= value,?) when appropriate. - Avoid combining many parameter properties with heavy constructor logic — it hurts readability.
- Reach for a TypeScript modifier rather than
#private, which cannot appear in parameters.