Ma Jerez
Ma Jerez

Reputation: 6005

Optional chaining on the left side in Javascript

Is it possible to use the optional chaining operator in the left side of an assignment = in Javascript?

const building = {}
building?.floor?.apartment?.number = 3; // Is this possible?

Upvotes: 72

Views: 31331

Answers (6)

jazzgil
jazzgil

Reputation: 2366

No, but you can do:

(building?.floor?.apartment ?? {}).number = 3;

Which will revert the assignment to a temporary object, effectively discarding the assignment if not all objects leading to it actually exist.

Note that the right side will always be performed, even if the left side is temporary. To avoid that, use a condition before the assignment:

if (building?.floor?.apartment)
    building.floor.apartment.number = 3;

(The condition assumes apartment, if exists, must be an object...)

Upvotes: 6

L. Cornelius Dol
L. Cornelius Dol

Reputation: 64026

It's not possible to do it (though as others have pointed out, it is logically defensible). The most succinct way I have found is to use conditional short circuiting, like this:

const building = {}
building?.floor?.apartment && (building.floor.apartment.number = 3); // This is possible!

You could slightly improve upon the above with a little helper if you need to do this often:

function optAssign(tgt,nam,val) { 
    tgt && (tgt[nam] = val);
    }

optAssign(building?.floor?.apartment,"number",3);

Upvotes: 0

jcalz
jcalz

Reputation: 327614

There is a proposal (in stage 1) to support it: https://github.com/nicolo-ribaudo/proposal-optional-chaining-assignment


It's not possible, sorry.

In the interest of a canonical answer: The MDN documentation isn't explicit about this, but you can read the proposal's README in GitHub for more information. It says:

The following is not supported, although it has some use cases; see Issue #18 for discussion:

  • optional property assignment: a?.b = c

In the linked issue are the comments 1:

OK, seems like there's a rough agreement in this thread not to do the write case in the first iteration.

and 2:

We also discussed this question at TC39, and the committee did not seem so interested in adding this feature.

So I guess it's not likely to happen anytime soon.

Hope that helps; good luck!

Upvotes: 44

zsytssk
zsytssk

Reputation: 722

Now, it is not possible. but you can try other way.

eg1:

let a = {x: {y: {z: 2}}} as {x?: {y?: {z?: number}}};
a?.x?.y?.z ? a.x.y.z = 1 : null;

eg2: use lodash

let a = {}
_.set(a, 'x.y.z', 1);
_.get(a, 'x.y.z');

Upvotes: 3

krysov
krysov

Reputation: 136

I was looking myself into this and regretfully, as the other commenters already stated, an assignment via optional chaining appears to not be possible in typescript as of the time of writing and will indeed yield you the parser warning:

The left-hand side of an assignment expression may not be an optional property access.

When attempting something akin to:

class A{
    b?: string;
}

var a = new A();
a?.b = "foo";

But since optional assignments are useful at times, there's still the old fashioned way of using single line if-queries like so:

class A{
    b?: string;
}

try{a.b = "foo0"} // throws TypeError
catch(e){console.log(e.toString())} // a is undefined

if(a) a.b = "foo1"; // skips
console.log(a); // a is undefined

var a: any;
if(a) a.b = "foo2"; // skips
console.log(a); // a is undefined

a = null;
if(a) a.b = "foo3"; // skips
console.log(a); // a is null

a = undefined;
if(a) a.b = "foo4"; // skips
console.log(a); // a is undefined

a = new A();
if(a) a.b = "foo5"; // runs
console.log(a); // a is A: {"b": "foo5"}

if(null) console.log("bar0"); // skips
if(undefined) console.log("bar1"); // skips
if({}) console.log("bar2"); // runs
if({something: "there"}) console.log("bar3"); // runs
if([]) console.log("bar4"); // runs
if(["not empty"]) console.log("bar5"); // runs

To demonstrate an example where it is in fact possible, here's a kotlin snippet:

class A{
    var b: B? = null;
    var title: String? = null;
    override fun toString():String = "Instance of class A with title: ${this.title} and b of value: ${this.b.toString()}";
}

class B{
    var title: String? = null;
    override fun toString():String = "Instance of class B with title: ${this.title}";
}

fun main() {
    var a:A? = null;
    println(a); // null
    
    try{a!!.title = "foo0"} catch(e:Exception){println(e)} // NPE
    
    a?.title = "foo1";
    println(a); // null
    
    a = A();
    println(a); // Instance of class A with title: null and b of value: null
    
    a?.title = "foo2";
    println(a); // Instance of class A with title: foo2 and b of value: null
    
    try{a!!.b!!.title = "bar0"} catch(e:Exception){println(e)} // NPE
    
    a?.b?.title = "bar1";
    println(a); // Instance of class A with title: foo2 and b of value: null
    
    a?.b = B();
    println(a); // Instance of class A with title: foo2 and b of value: Instance of class B with title: null
    
    a?.b?.title = "bar2";
    println(a); // Instance of class A with title: foo2 and b of value: Instance of class B with title: bar2

    a?.b?.let{it.title = "bar3"}
    println(a); // Instance of class A with title: foo2 and b of value: Instance of class B with title: bar3
}

Now I'm not arguing that one language is better than the other but rather that purpose and philosophy require different design choices and decide the resulting shape of the tool which ultimately any programming or script language is. While also being slightly annoyed that I can not assign values to an optional chaining in typescript.

Edit: I used these Playground sites which are useful to test such things:

Upvotes: 9

Philipp Meissner
Philipp Meissner

Reputation: 5482

No, it is not possible. The documentation clearly states

The optional chaining operator ?. permits reading the value of a property located deep within a chain of connected objects without having to expressly validate that each reference in the chain is valid.

Upvotes: 1

Related Questions