29

I have the following TypeScript snippet:

class Jedi { 
  private readonly name: string = 'Skywalker';
  constructor(name: string) { 
    this.name = name; //Why I am able to modify name.
  }
  toString():string {
    return this.name;
  }
}

var jedi = new Jedi('Kenobi');

console.log(jedi.toString()); //Kenobi

As you can see in the code, I have declared name property as readonly. As per my knowledge to declare properties as constant we use readonly in TypeScript. Once we declare it with initialization, we can not modify it.

But as you can see in the constructor it's being modified. Also TypeScript compiler also does not warn about it. Not sure whether it's a bug in TypeScript or it's intentional. Can anyone please explain?

5
  • Once you've set the value you can't re-assign another value. Commented Feb 13, 2017 at 10:11
  • But i am able to assign it. Not sure why it's let me do so. Commented Feb 13, 2017 at 10:19
  • 1
    Sorry, I missed that part.. Thought you wrote it doesn't let you.. Commented Feb 13, 2017 at 10:22
  • no issues...... Commented Feb 13, 2017 at 10:22
  • 5
    Essentially, there is only one place where a readonly class member can be assigned to, and that's the constructor. However, direct assignments to class members at their declaration also ultimately happen in the constructor, hence why it's allowed (i.e. TypeScript doesn't differentiate between the two). If you check out the compiled JavaScript output, you'll understand why this is the case. The direct member assignments are effectively "inserted" into the top of the constructor (but after any super calls). Commented Feb 13, 2017 at 11:13

3 Answers 3

25

The docs only say

Readonly properties must be initialized at their declaration or in the constructor. (Source)

But as Romain pointed out there is a little more info in the TypeScript 2.0 release notes:

Read-only properties may have initializers and may be assigned to in constructors within the same class declaration, but otherwise assignments to read-only properties are disallowed. (Source)


But what confuses me is the compiled output. See an example on the TS Playground.

As you can see the initially (readonly) property is overwritten without any warning from TS at compile time. I guess this is intentional, because the language behaves the same all the time and there are less edge cases.

EDIT: There is also a "sugarized" way to initialize a class with properties in TypeScript (I guess you know that but anyway):

Adding public/private to arguments of the constructor signature will initialize those arguments as class properties. So in your example:

class Jedi { 
    constructor(
        private readonly name: string = 'Skywalker'
    ) {}

    toString():string {
        return this.name;
    }
}

And if let TypeScript compile the above code, it actually works as expected (or at least what I would expect). There is another Link to the Playground.

tl;dr; Initializing class properties inside the constructor signature via public/private will set the default value if none is given. This does not work if you set class properties "by hand" (this.name = name). The later might still not be a bug but rather an intentional behaviour, because you explicitly set the class property.

You can avoid this issue (or at least have TypeScript scream at you) by enabling noImplicitAny and strictNullChecks. This way TypeScript will let you know that your class property might be undefined.


What I would suggest is not using readonlyproperty if you want to have immutability. Not only is the above mentioned kinda weird behaviour, some less experienced developers might also think that a readonly array/object is really fully frozen/read only where it isn't. Rather use something like ImmutableJS.

Sign up to request clarification or add additional context in comments.

3 Comments

Here's a bonus reason why you might want to avoid using readonly. It can be sidestepped.
Simply adding readonly will already add the property to the class, no access modifier necessary. Example
Man, a readonly class property declared as a constructor parameter is such a strange design decision.
2

From the TypeScript documentation:

Read-only properties may have initializers and may be assigned to in constructors within the same class declaration, but otherwise assignments to read-only properties are disallowed.

More about the readonly keyword

Comments

1

Member variables that are declared as readonly can be assigned with their declaration, or from the constructor. There's a certain C# familiarity with this.

Example 1

Here, the readonly member variable is assigned directly, which is useful if you know what a value is, and that it's never going to change.

class Foo {
    public readonly bar: number = 123;
}

Example 2

Here, the readonly member variable is declared, and then assigned from the constructor, which is useful if you don't yet know the final value, but some constructor logic will resolve this for you.

class Foo {
    public readonly bar: number;

    constructor(bar: number) {
        this.bar = 123 * bar / 100;
    }
}

Example 3

Here the readonly member variable uses TypeScript's short-hand member variable declaration, where global member variables can be assigned from the constructor, which is useful if you want the user to input a value which will then never change. In this particular example, bar can either be set by the user, or will default to 123.

class Foo {
    constructor(public readonly: bar: number = 123) {
    }
}

Example 4

This is something you don't want to do, because the emitted JavaScript causes two assignments to the same variable. I'm just adding this so that future developers know they probably shouldn't do this.

class Foo {
    public readonly bar: number = 123;

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

The emitted JavaScript will look something like this...

var Foo = (function () {
    function Foo(bar) {
        this.bar = 123; // :)
        this.bar = bar; // :(
    }

    return Foo;
})();

What about immutability?

readonly is only upheld by TypeScript and it's compiler; there's no readonly in JavaScript (well, that's not entirely true, just that TypeScript doesn't emit truly readonly values). There are a couple of workarounds for this (and yes, it would be nice if TypeScript could emit these for us!)

Creating an immutable member variable

class Foo {
    // TypeScript respects "readonly"
    public readonly bar: number;

    constructor(bar: number) {
        // JavaScript respects property that isn't writable.
        Object.defineProperty(this, "bar", {
            value: bar,
            enumerable: true,
            configurable: false,
            writable: false
        });
    }
}

Creating an immutable class

class Foo {
    constructor(public readonly bar: number) {
        // Nothing in the object is allowed to be reassigned.
        Object.freeze(this);
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.