18

I have a simple class, and I want to assign a value to a readonly property in a method initiated by the constructor, but it says [ts] Cannot assign to 'readOnlyProperty' because it is a constant or a read-only property. Why can't I assign a value to the property even though I am calling process from the constructor?

Sample Code:

class C {
    readonly readOnlyProperty: string;
    constructor(raw: string) {
        this.process(raw);
    }
    process(raw: string) {
        this.readOnlyProperty = raw; // [ts] Cannot assign to 'readOnlyProperty' because it is a constant or a read-only property.
    }
}
Erik Philips
  • 53,428
  • 11
  • 128
  • 150
Bill
  • 553
  • 1
  • 7
  • 19
  • 2
    How is the TypeScript compiler supposed to infer that `process()` will only ever be called from the constructor? – Robby Cornelissen Sep 21 '18 at 05:27
  • @RobbyCornelissen while a valid concern in this case, the same problem happens with an anonymous callback inside the constructor, e.g. when using array methods. – Emile Bergeron Aug 10 '22 at 17:35

5 Answers5

12

I usually use this workaround:

get myValue(): boolean { return this._myValue }
private _myValue = true

Result:

  • myValue – is readonly from the outside
  • _myValue – can be modified within the class

The advantage of this workaround is that your IDE can refactor your property. Also, we do not misuse a non-writable readonly property, which can lead to errors when compiling, optimizing, or code review. If not today, then in the near future. That's why I wouldn't use something like:

// hack, may cause problems (refactoring is not possible)
(this as any).readOnlyProperty = raw

// hack, may cause problems (we write a non-writable)
(this.readOnlyProperty as boolean) = raw

// hack, may cause problems (refactoring is not possible)
Object.assign(this, {readOnlyProperty: raw})
Jan
  • 424
  • 5
  • 15
12

Instead of casting this as any, you should enforce type checking by modifying just this property:

// OK
(this.readOnlyProperty as string) = raw; 

// TS2322: Type '5' is not assignable to type 'string'.
(this.readOnlyProperty as string) = 5;   
GROVER.
  • 4,071
  • 2
  • 19
  • 66
Michael Chen
  • 149
  • 1
  • 4
  • I think this is the cleanest most efficient answer – tru7 Feb 11 '20 at 13:41
  • @tru7 I am really wondering. You have to recast the property if you want to set it. How can this be the cleanest most efficient way? Especially if you work with classes! I would call chens way a bit hacky. Look at my answer, there is already a classic way to solve this use case. – Jan Dec 09 '22 at 12:48
1

When you create a separate function to assign the value, this separate function can be used from somewhere else than the sole constructor. The compiler will not check (and for a public function, will not even be able to check) that the function is only called from constructor. So the error.

You have 2 workarounds to assign the value anyway. The cleaner would be to put the core of your separate function into the constructor. The other one, which will make you loose the type checking and so is not recommanded unless you really know what you are doing would be to convert this into any:

(this as any).readOnlyProperty = raw
Stéphane Veyret
  • 1,791
  • 1
  • 8
  • 15
0

Pretty old question but thought it was still worth sharing how I usually get past this. I tend to return the value you want to set from the method, then you're still separating the logic, while also keeping readonly without any arguably illegal bypasses. e.g...

class C {
    readonly readOnlyProperty: string;
    constructor(raw: string) {
        this.readOnlyProperty = this.process(raw);
    }
    process(raw: string) {
         // ...some logic that processes 'raw'...

        return raw;
    }
}
LukeLucas
  • 87
  • 1
  • 7
0

just try

Object.assign(this, {readOnlyProperty: raw})
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
  • 1
    While this does make the TypeScript warning go away as requested in the question the best answers are more than just code. It's helpful to other readers that may be facing the same problem to understand how this answer works and why it might be used instead of other answers. – anothermh Dec 19 '22 at 22:06