'How to write a a setter for an optional propery in TypeScript which only allows to set a non-empty value?
I'm trying to make an optional property in a TS class and create a setter and a getter for it. It is implied that the property can only be set once, so I'm trying to do something like this:
private _signer?: Signer
get signer(): Signer | undefined { return this._signer }
set signer(signer: Signer) {
if(!this.validateSignerSecret(signer)) throw Error("Invalid signer, won't set")
if(!this.signer) {
this._signer = signer;
return;
}
if(JSON.stringify(this.signer) !== JSON.stringify(secret)) throw Error("Another signer is set already, won't update")
}
However, TS copmlains that
The return type of a 'get' accessor must be assignable to its 'set' accessor type Type 'undefined' is not assignable to type 'Signer'
I can change this to
set signer(signer: Signer | undefined) {
but it's not what I really need: I'd like to only allow to set a non-empty Signer while the getter can return undefined.
Can I somehow keep the prop optional and allow to set only non-empty one?
Solution 1:[1]
Unfortunately, that is not supported. It used to be that getters and setters had to operate on exactly the same type. They relaxed that in this change, but they deliberately made it so that the getter type must be assignable to the setter type. They were aware that it made cases like yours not work.
From the linked page (emphasis added):
Restrictions
As a conservative implementation, a few restrictions are in place.
First, the type of the getter must be assignable to the type of the setter. In other words, the assignment
obj.x = obj.x;must be legal assuming obj.x is writable at all.
This restriction closes off a certain set of use cases for properties that start out "uninitialized" (usually null/undefined) but then only want "initalizing" assignments to occur. We'll continue to examine these to see if those use cases are prevelant enough to warrant opening this up more.
These prevent novel unsoundness from occurring and makes this feature unimpactful from a type relational perspective, which limits its risk.
Solution 2:[2]
The other answers explain the state of the compiler restrictions. Here is how you can accomplish your goal:
You can use a @ts-ignore or @ts-expect-error comment directive to suppress the compiler restriction and create the types that you want:
declare const s: Signer;
declare class C {
private _signer?: Signer
// @ts-expect-error
get signer (): Signer | undefined;
set signer (signer: Signer);
}
const c = new C();
c.signer; // Signer | undefined;
c.signer = s; // ok
c.signer = undefined; /* ?
~~~~~~~~
Type 'undefined' is not assignable to type 'Signer'.(2322) */
Solution 3:[3]
Currently, getter & setter have to have matching types.
But it's in talk to loosen the get/set relation
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | |
| Solution 2 | jsejcksn |
| Solution 3 | Matthieu Riegler |
