I want to be able to make readonly
properties (not getters) for users of my class, but I need to update them internally; is there a way to do this and allow to
The answer posted by OP here is the best answer, not this one. Which is just to use an interface and not export it.
interface IGraphObjectInternal { _parent: GraphNode; } export class GraphNode implements IGraphObjectInternal { // tslint:disable-next-line:variable-name // tslint:disable-next-line:member-access // tslint:disable-next-line:variable-name public readonly _parent: GraphNode; public add(newNode: GraphNode) { (newNode as IGraphObjectInternal)._parent = this; } }
I tried this earlier and had some problem (not sure why, but tried again now and it works just fine.
Leaving the answer here just for the fun of playing with it.
TypeScript provides readonly
keyword which allows setting value on initialization or in constructor only.
If you want to change the value any time, then what you need is a read-only property, which is known as a "get" property.
Example:
class MyClass {
private _myValue: WhateverTypeYouWant;
get myValue() {
return this._myValue;
}
doSomething(inputValue: WhateverTypeYouWant) {
// Do checks or anything you want.
this._myValue = inputValue; // Or anything else you decide
}
}
It's worth mentioning that users may still be able to call myObject['_myValue']
and access the property. TypeScript will not tell them about it in intellisense though, and if they do use your code this way, they are using your library in an unsupported way and shooting themselves in the foot (note that this is client-side code anyway, so the code is available to read).
Check the official documentation on how this works.
If you really want to use readonly
and force it to work, you can do it like this:
class Test {
readonly value = "not changed";
changeValue() {
this["value" as any] = "change from inside";
}
}
But as I mentioned in my comment on this answer, and I show in the runnable version of this example, the semantics are the same in the sense that both private
and readonly
can be changed from outside if the users really want to.
In further comments you bring an interesting scenario, game development, where function call is considered expensive. I cannot validate how expensive property access might be (Which is the recommended path for this generally), but here's the answer I think you are looking for:
If you really really want to do set the readonly
member, and just want to make sure you have refactoring support, change this["value" as any] =
to (this.value as Test['value']) =
(where Test
here is the class name, and value
is the property name).
class Test {
// We had to set the type explicitly for this to work
// Because due to initial `= "not changed"`
// `value` property has type `"not changed"` not `string`
readonly value: string = "not changed";
changeValue() {
(this.value as Test['value']) = "change from inside";
alert(this.value);
}
}
const test = new Test();
test.changeValue();
(test.value as Test['value']) = 'change from outside';
alert(test.value);
Runnable Example
Although the syntax (this.value as Test['value']) =
works in official TypeScript playground, as proven by the link at the end of Update 2 in this answer, it doesn't work in VS Code (and maybe other TS environments).
You need to change it to this['value' as Test['value']] =
(where, again, Test
is a class name and value
is a property name).
The working code becomes:
class Test {
// We had to set the type explicitly for this to work
// Because due to initial `= "not changed"`
// `value` property has type `"not changed"` not `string`
readonly value: string = "not changed";
changeValue() {
this['value' as Test['value']] = "change from inside";
alert(this.value);
}
}
const test = new Test();
test.changeValue();
test['value' as Test['value']] = 'change from outside';
alert(test.value);
Runnable Example
Since refactoring is the reason for asking the question I have to mention that besides being ugly, the workaround here offers only limited refactoring support.
That means, if you misspell property name (value
in the sample) in any part of the assignment this['value' as Test['value']] = ...
, TypeScript will give you a compile time error.
The problem though is, at least in VS Code in my quick test, when you rename the property (from value
in the sample to anything else), TypeScript / VS Code doesn't update the references to it that are implemented using this workaround.
It still gives you a compile time error, which is better than leaving invalid code without errors, but you'd want it to rename the property for you too.
Luckily having to do this with a string replace (of ['value' as Test['value']]
in the sample) seems to be generally safe from false matches, but still, it's silly, and less than desired, but I think this is as far as this gets.