41

Is there any operator in the typescript which is used similer to Elvis Operator of angular2, i mean to say let supppose i have to get key from object like this:

this.myForm.name.first_name

and in case first_name does't exist so it will throw error first_name of undefined,

yes i can handel this error using Ternary operator of typescript like this

this.myForm.name ? this.myForm.name.first_name : ''

but sometimes keys are getting too long,

so is there any operator like Elvis Operator of angular2 in the typescript so that i can use like this

this.myForm?.name?.first_name
str
  • 42,689
  • 17
  • 109
  • 127
Pardeep Jain
  • 84,110
  • 37
  • 165
  • 215
  • 3
    downvote why ? is this not valid question ? – Pardeep Jain Aug 31 '16 at 10:46
  • 5
    You're referring to the [safe navigation operator (?.)](https://en.wikipedia.org/wiki/Safe_navigation_operator). [Elvis operator (?:)](https://en.wikipedia.org/wiki/Elvis_operator) is a ternary shorthand for implicitly evaluating to the first operand if true. – Drazen Bjelovuk Aug 23 '17 at 13:55
  • @DrazenBjelovuk And I'm looking for the answer on Elvis operator, but can't seem to find one... Nor JS, nor TS. – s3c Feb 18 '22 at 07:53
  • @s3c The elvis operator equivalent in JS/TS is `||`. – Drazen Bjelovuk Feb 18 '22 at 16:09
  • 1
    I should have connected these dots myself. *embarassed* Thank you – s3c Feb 21 '22 at 00:53

3 Answers3

39

Update December 2019: TypeScript 3.7 introduced Optional Chaining which is equivalent to the safe navigation operator known in other languages. The ECMAScript proposal optional chaining has reached stage 4 and will thus be part of the specification in ES2020. See mdn: Optional chaining for more information.


Update July 2017: As JGFMK pointed out in the comments, there is an ECMAScript proposal called Optional Chaining for JavaScript. If/when the proposal reaches Stage 4, it will be added to the language specification.


There is neither a safe navigation nor elvis operator in TypeScript and, as far as I know, nothing comparable, either.

For a reference see the feature request at Suggestion: "safe navigation operator", i.e. x?.y. The explanation for not implementing it is the following (which, in my opinion, is a valid reason):

Closing this for now. Since there's not really anything TypeScript-specific that would require this at expression level, this kind of big operator change should happen at the ES spec committee rather than here.

The general tripwires for re-evaluating this would be a concrete ES proposal reaching the next stage, or a general consensus from the ES committee that this feature wouldn't happen for a long time (so that we could define our own semantics and be reasonably sure that they would "win").

Alternatives to that notation would be to use the logical AND operator, try/catch or a helper function like getSafe(() => this.myForm.name.first_name) as described in this post.

str
  • 42,689
  • 17
  • 109
  • 127
  • thanks for your answer, is there any alternate for this apart from ternary operator ? – Pardeep Jain Aug 31 '16 at 10:57
  • 1
    @PardeepJain What you could do is to assign empty objects when certain properties are undefined: `this.myForm.name = this.myForm.name || {}`. Then you can be sure that `name` is defined. Another way is to define a default data structure and then overwrite it with your actual data using [`Object.assign`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign). – str Aug 31 '16 at 11:11
  • 1
    There is this https://github.com/tc39/proposal-optional-chaining It's still in proposal stage. Didn't realise this was an Angular tweak... Presumed it was baked into Typescript... – JGFMK Jul 19 '17 at 06:16
  • Duck typing is just a form of run-time type checking for object types, just as `typeof`/`instanceof`. I wonder why it is necessary in TypeScript, since you should know your types. –  Aug 22 '17 at 09:43
  • @ftor Types in TypeScript can be `null` or `undefined` unless you set `strictNullChecks` to true. – str Aug 22 '17 at 12:41
  • optional chaining is not elvis operator! however, it is what they call `Nullish Coalescing` in the article you posted (right next to the optional chaining) so its also there :) Its usually marked as `?:` but for some reason they go with `??` – Julius ShadowAspect Flimmel Jan 06 '20 at 11:33
  • @JuliusShadowAspectFlimmel Corrected. – str Jan 06 '20 at 12:29
8

Repeatedly OR it with an empty object

I can't live without Elvis when writing HTML that references properties/subproperties that may or may not exist.

We can't write

this.myForm?.name?.first_name

I guess the people in charge of ES are probably spending most of their time actually writing JS, and not writing bits of front-end HTML that accesses object properties, hence their bemusement as to why anyone would need safe navigation.

They argue that "Nobody needs it", "It allows errors to be silent and could thereby bite you later" and "How would you interpret x?(y) ?"

My solution, while I wait for a future ES specification to include the Elvis operator, is as follows:

(((this.myForm || {}).name || {}).first_name )

In short, at every stage that a value could be undefined, you OR it with a {}, so that the undefined becomes an empty object. Once you put that whole thing into parentheses, you are safe to try to extract a property of it, since any property you try to extract from {} is simply undefined rather than an actual error.

It does get a bit ugly when you have multiple tiers, but in many cases it is less ugly than having to use a series of && to progressively step down through the levels of the object.

For array access, OR it with an empty array

If navigating into an array, for example if the form had many names and you wanted to select the first, the approach is similar:

((((this.myForm || {}).names || [])[0] || {}).first_name )

What happens if something in the chain is 0 or ""?

Suppose you were expecting to read a value "John" from this.myForm.names[0].first_name.

The typical situation that would normally trigger an error is where there is simply no object this.myForm. Instead of having an error, the formula I described above would work, and be interpreted as {}.first_name, which is undefined (but not an error).

Now imagine a hostile person has sneakily set this.myForm to 0 or "" or {}. Will we now see an error? No, because the values 0 or "" are considered falsy by Javascript, and {} is truthy but is itself {}, so this.myForm || {} evaluates to {}, and the remainder of the chain falls through to defaults as before, and the method still works, returning undefined rather than an error.

Example

console.log("Each example shows first the result WITH the workaround, and then WITHOUT.")

console.log("a. This should work normally.")
a = { myForm:{ names:[ { first_name:"John", surname:"Smith"}]} };
console.log(a.myForm.names[0].first_name)
console.log ((((a.myForm || {}).names || [])[0] || {}).first_name )

console.log("b. We have removed the first_name property. This still works.")
b = { myForm:{ names:[ {                    surname:"Smith"}]} };
console.log(b.myForm.names[0].first_name)
console.log ((((b.myForm || {}).names || [])[0] || {}).first_name )

console.log("c. We have removed the entire 'names' array. This is fixed by the workaround, but gives an error if run without the workaround.")
c = { myForm:{ } };
console.log ((((c.myForm || {}).names || [])[0] || {}).first_name )
console.log(c.myForm.names[0].first_name)

    console.log("d. Now the whole root object is empty. Again the workaround gets around this, but without the workaround, we get an error.")
    d = { };
    console.log ((((d.myForm || {}).names || [])[0] || {}).first_name )
    console.log(d.myForm.names[0].first_name)
ProfDFrancis
  • 8,816
  • 1
  • 17
  • 26
  • haven't tried yet, but seems much cleaner hack. Thanks for sharing. Would be better if you provide working example as well may be stackblitz or plunker etc. – Pardeep Jain Oct 15 '18 at 09:22
  • 3
    Attention: this response is no longer up to date because `?.` has been introduced in the meantime by TypeScript 3.7 ! – Didier68 Jan 08 '20 at 10:56
  • But we plain Javascripters can still use it 8-) – ProfDFrancis Mar 14 '20 at 19:53
  • That will create hard to find, unexpected errors if you hit a non-null value that can be auto-converted to a boolean, like "0", "", or [0]. – Brixomatic Oct 05 '20 at 10:00
  • @Brixomatic. I tried to edit in your point but then I realised I couldn't find a situation in which my formula does not work. If I try 0 or "" or [0], the formula still works without error. Can you give a counterexample? – ProfDFrancis Oct 13 '20 at 13:57
  • 1
    @Eureka My bad, I stand corrected. The unexpected bit being that "0" == false, but ("0" || "") === "0", although (false || "") === "". It's a slippery slope, leading to wrong/unexpected conclusions. – Brixomatic Oct 13 '20 at 18:31
1

Here's a solution that looks a bit more verbose:

const elvis = (...xs: (() => any|null)[]): any => {
    if(xs.length == 0) return null;
    const y = xs[0]();
    return xs.length == 1 || y == null ? y : elvis(...xs.slice(1));
};

const bla= {
    a : 'hello',
    b : null,
    c: undefined
}

console.log("a:", elvis(() => bla));
console.log("a:", elvis(() => bla, () => bla.a));
console.log("a:", elvis(() => bla, () => bla.a, () => bla.a.length));
console.log("b:", elvis(() => bla, () => bla.b, () => bla.b.length));
console.log("c:", elvis(() => bla, () => bla.c, () => bla.c.length));

Output:

> a: {a: "hello", b: null, c: undefined}
> a: hello
> a: 5
> b: null
> c: undefined
Brixomatic
  • 381
  • 4
  • 16