Skip to content
Theory · Classes · lesson 5 of 12

readonly and parameter properties

You have already seen the readonly modifier on the properties of an object type. On a class field it works the same way. In the blueprint picture it is the house number on the front. It is put up while the house is being built and is never moved afterward, or the mail would go to the wrong place.

TypeScript
class Order {
    readonly id: number;
    total = 0;

    constructor(id: number) {
        this.id = id;
    }
}

const order = new Order(7);
order.total = 150;
console.log(order.id, order.total); // 7 150

Let's go through the example line by line:

  1. readonly id: number declares a read-only field.
  2. The constructor writes id into it, and that is allowed: the number goes up while the house is being built.
  3. order.total = 150 changes an ordinary field, and the compiler does not object.
  4. The program prints 7 150.

Writing to a readonly field

A readonly field can be written in only two places: in its declaration and in the constructor. Any write after that is an error, whether it is order.id = 8 outside or this.id = next in a method of the class itself:

TEXT
error TS2540: Cannot assign to 'id' because it is a read-only property.

"You cannot assign to id because it is a read-only property." Only tsc checks this rule. In the JavaScript id stays an ordinary field, and at run time the write works.

Parameter properties

Look at the class Order again: the name id is written in it three times. First the field declaration, then the constructor parameter, then the assignment this.id = id. The more fields there are, the more of this repetition you get.

TypeScript has a shorthand for it. A parameter property is a constructor parameter with the modifier public, private, protected or readonly. Such a parameter declares a field with the same name and puts the argument into it:

TypeScript
class User {
    constructor(private readonly id: number, public login: string) {}

    describe(): string {
        return `${this.id}: ${this.login}`;
    }
}

const user = new User(7, "anna");
console.log(user.describe(), user.login); // 7: anna anna

Let's take the constructor apart:

  • private readonly id: number declares a private read-only field id;
  • public login: string declares a public field login, and the word public is required here: without a modifier the parameter would stay an ordinary parameter;
  • the constructor's body is empty: tsc writes the assignments itself.

Here is the JavaScript tsc writes from this file:

JavaScript
"use strict";
class User {
    id;
    login;
    constructor(id, login) {
        this.id = id;
        this.login = login;
    }
    describe() {
        return `${this.id}: ${this.login}`;
    }
}
const user = new User(7, "anna");
console.log(user.describe(), user.login); // 7: anna anna

The code has gained the field declarations id and login and two assignments in the constructor. They were not in the .ts file: tsc added them itself.

Interviewers ask about parameter properties like this: what does constructor(private name: string) {} create? The answer: a private field name and an assignment of the argument to it.

node file.ts will not run them

Usually, to get JavaScript, it is enough to cut the types out of a file. With parameter properties that does not work: new lines have to be written. That is why node user.ts, which can only cut types out, refuses to run such a file:

TEXT
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript parameter property is not supported in strip-only mode

The code has no error, and tsc compiles it. Compile the file with tsc user.ts and run node user.js, or try the code in the TypeScript Playground.

If a project is run with node file.ts, it does without parameter properties: fields are declared and assigned as in the class Order.

Common mistake

The modifier on the parameter was forgotten:

TypeScript
class User {
    constructor(login: string) {}

    describe(): string {
        return this.login; // ✗
    }
}
TEXT
error TS2339: Property 'login' does not exist on type 'User'.

Without a modifier login is an ordinary parameter: it lives only inside the constructor, and no field comes of it. Add the modifier: constructor(public login: string) {}.

Key points

  • A readonly field is written in its declaration or in the constructor and only read after that, otherwise you get error TS2540.
  • A constructor parameter with public, private, protected or readonly becomes a field, and tsc writes the assignment itself.
  • A parameter without a modifier stays an ordinary parameter and does not become a field.
  • node file.ts does not run a file with parameter properties: you need tsc or the Playground.