Parameter properties

Declares and assigns a class field directly from a constructor parameter marked public, private or readonly.

Signature
class C {
  constructor(
    public readonly x: number,
    protected y: string,
    private z: boolean = false
  ) {
    // no body necessary
  }
}
  • Added in TypeScript 1.0

Parameters

ParameterTypeDescription
$public | private | protectedoptionalaccessibility modifierDeclares the field and sets its visibility. At least one modifier (or readonly) must be present, otherwise the parameter stays an ordinary argument.
$readonlyoptionalmodifierMarks the generated field readonly. May appear alone or after an accessibility modifier.
$nameidentifierThe parameter name, reused verbatim as the field name. Destructuring patterns are not allowed here.
$defaultoptionalTypeOrdinary parameter default. It makes the argument optional and is what the field receives when the caller omits it.

Errors

A parameter property is only allowed on the constructor implementation — putting one on a constructor overload signature, on an ambient declaration, or on a destructuring pattern is an error.

Changelog

  • PHP 4.3The override modifier may be combined with a parameter property when it redeclares a base-class field.

Prefixing a constructor parameter with public, private, protected or readonly tells TypeScript to declare a field of the same name and assign the argument to it. The field declaration, the assignment and the parameter collapse into one line, and the constructor body can stay empty.

When to reach for it

  • Constructor-injected dependencies — the pattern Angular and NestJS are built around.
  • Small immutable value objects, where public readonly says everything the class needs to say.
  • Any class whose constructor was three lines of this.x = x.

What trips people up

The visibility is compile-time only. A private parameter property is an ordinary enumerable own property at runtime, so it shows up in Object.keys, object spread and JSON.stringify — secrets leak into logs this way. Use a # field if you need real privacy. The modifier is also only legal on the constructor implementation: if you write constructor overloads, the shorthand cannot go on the signatures. Destructured parameters are out too, so constructor(private { a, b }) is not a thing. And ordering matters when a field initializer elsewhere in the class reads one of these fields, since the assignment happens inside the constructor rather than at declaration.

Example

Try it yourself
Loading editor…
Press Run to execute the code.

When to use it

  • Inject services into an Angular or NestJS class without repeating each name three times.
  • Define an immutable value object such as Point or Money with `public readonly` parameters and no constructor body.
  • Keep a private collaborator on the instance without writing a separate field declaration.

Frequently asked questions

What are parameter properties in TypeScript?

They are constructor parameters marked with public, private, protected or readonly, which makes TypeScript declare a class field of the same name and assign the argument to it automatically. The constructor body can then be empty.

Is a private parameter property actually private at runtime?

No — the modifier is erased at compile time, so the field is an ordinary enumerable own property that Object.keys, object spread and JSON.stringify all expose. Use a `#` private field when you need privacy the JavaScript engine enforces.

Why can't I use a parameter property on a constructor overload?

Because the shorthand generates an assignment, and overload signatures have no body to generate it into — only the implementation signature does. Declare the modifier on the implementation constructor and leave the overload signatures as plain parameters.

More examples

What the shorthand expands to

Both print through the same path: "user:ada-7" then "acct:ada-7". The two classes are identical after compilation — the shorthand emits exactly the field declarations and constructor assignments written out in the second class, which is why a default value on the parameter becomes the field's value when the argument is omitted.

Example · typescript
// Shorthand
class UserServiceShort {
  constructor(
    private readonly repo: { find(id: number): string },
    private prefix = "user"
  ) {}
  label(id: number): string { return `${this.prefix}:${this.repo.find(id)}`; }
}

// The exact equivalent, written out
class UserServiceLong {
  private readonly repo: { find(id: number): string };
  private prefix: string;
  constructor(repo: { find(id: number): string }, prefix = "user") {
    this.repo = repo;
    this.prefix = prefix;
  }
  label(id: number): string { return `${this.prefix}:${this.repo.find(id)}`; }
}

const repo = { find: (id: number) => `ada-${id}` };
console.log(new UserServiceShort(repo).label(7));
console.log(new UserServiceLong(repo, "acct").label(7));

Where the shorthand is rejected

Prints 21, -4, and {"input":21}. The last line is the leak worth remembering: `input` is declared private, yet it serialises like any other property, because the modifier exists only for the type checker. The commented-out lines show the two placements tsc rejects outright — overload signatures and binding patterns.

Example · typescript
class Temperature {
  // Overload signatures: no parameter properties allowed here.
  constructor(celsius: number);
  constructor(raw: string);
  // Implementation: this is the only place the modifier may appear.
  constructor(private input: number | string) {}

  get celsius(): number {
    return typeof this.input === "string" ? Number(this.input) : this.input;
  }
}

console.log(new Temperature(21).celsius);
console.log(new Temperature("-4").celsius);

// Also rejected by tsc:
//   constructor(private { a, b }: { a: number; b: number }) {}
//   Error: A parameter property may not be declared using a binding pattern.
console.log(JSON.stringify(new Temperature(21)));

Discussion

  • Be the first to comment on this lesson.