205

In Angular 2 templates safe operator ?. works, but not in component.ts using TypeScript 2.0. Also, safe navigation operator (!.) doesn't work.

For example:

This TypeScript

if (a!.b!.c) { }

compiles to this JavaScript

if (a.b.c) { }

But when I run it, I get the follow error:

Cannot read property 'b' of undefined

Is there any alternative to the following?

if (a && a.b && a.b.c) { }
Alexander Abakumov
  • 13,617
  • 16
  • 88
  • 129
Emerceen
  • 2,735
  • 3
  • 17
  • 22
  • 10
    The typescript operators only exist for compilation, they are not present in the compiled javascript. The error you posted is a runtime error. – Nitzan Tomer Oct 25 '16 at 11:05
  • o(o(o(o(test).level1).level2).level3 or o(o(o(o(a).b).c).d https://stackoverflow.com/a/35169378/3914072 this solution works great for us at compilation time and it's with safe types – Rajab Shakirov Apr 14 '18 at 19:44
  • Does this answer your question? [Does Typescript support the ?. operator? (And, what's it called?)](https://stackoverflow.com/questions/15260732/does-typescript-support-the-operator-and-whats-it-called) – Michael Freidgeim Jun 23 '21 at 13:38
  • this is such a difficult thing to Google. Thank you! – R Claven Sep 21 '21 at 01:53

7 Answers7

234

! is non-null assertion operator (post-fix expression) - it just saying to type checker that you're sure that a is not null or undefined.

the operation a! produces a value of the type of a with null and undefined excluded


Optional chaining finally made it to typescript (3.7)

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. The ?. operator functions similarly to the . chaining operator, except that instead of causing an error if a reference is nullish (null or undefined), the expression short-circuits with a return value of undefined. When used with function calls, it returns undefined if the given function does not exist.

Syntax:

obj?.prop // Accessing object's property
obj?.[expr] // Optional chaining with expressions
arr?.[index] // Array item access with optional chaining
func?.(args) // Optional chaining with function calls

Pay attention:

Optional chaining is not valid on the left-hand side of an assignment

const object = {};
object?.property = 1; // Uncaught SyntaxError: Invalid left-hand side in assignment
Aleksey L.
  • 35,047
  • 10
  • 74
  • 84
  • 14
    For those interested in checking in on the status of this. TypeScript is planning on adding it once ECMAScript adds support. ECMAScript is tracking this issue [here](https://github.com/tc39/proposal-optional-chaining). – Pace Dec 29 '17 at 18:54
  • o(o(o(o(test).level1).level2).level3 or o(o(o(o(a).b).c).d https://stackoverflow.com/a/35169378/3914072 this solution works great for us at compilation time and it's with safe types – Rajab Shakirov Apr 14 '18 at 19:45
  • playground of the new feature (will release on November 5): https://www.typescriptlang.org/play/?ts=3.7.0-pr-33294-11#code/JYOwLgpgTgZghgYwgAgKoGdrIN4FgBQyRycAJqVBOugPwBcR2yBxryCwYAng+mFKADmLNkQAOAez4BhCaQi9+QkUQC+BdfgLyEAGziV2EkH2QBXTFAYZoBAhegA6MhSq1HHbkA – Nisim Joseph Oct 15 '19 at 07:39
  • 1
    @aleksey-l: It might not be a bad idea to strikethrough the stuffs prior to the "last update". It will help people who don't read top to bottom (i.e. too fast)! :) – Aidin Jan 25 '20 at 09:47
  • 2
    This is so cool - I had no idea that you could use it for function calls. Was looking for a solution and came across your answer, so thank you! – Geoff Davids May 13 '20 at 13:57
156

Since TypeScript 3.7 was released you can use optional chaining now.

Property example:

let x = foo?.bar.baz();

This is equvalent to:

let x = (foo === null || foo === undefined)
  ? undefined
  : foo.bar.baz();

Moreover you can call:

Optional Call

function(otherFn: (par: string) => void) {
   otherFn?.("some value");
}

otherFn will be called only if otherFn won't be equal to null or undefined

Usage optional chaining in IF statement

This:

if (someObj && someObj.someProperty) {
  // ...
}

can be replaced now with this

if (someObj?.someProperty) {
  // ...
}

Ref: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-7.html

Audwin Oyong
  • 2,247
  • 3
  • 15
  • 32
Przemek Struciński
  • 4,990
  • 1
  • 28
  • 20
  • this keeps throwing **Expression expected** error when compiling, it works if I use `!` instead of `?` in "typescript": "~3.5.3" – Scaramouche Apr 09 '20 at 17:23
  • 5
    @Scaramouche this is because the optional chaining operator (`?.`) was introduced in 3.7 and you're running 3.5.3. In versions earlier than 3.7, TypeScript assumes that `?` is being used in the shorthand if form (`foo ? bar : baz`) and so expects the rest of the expression – George Apr 27 '20 at 21:32
24

Update:

Planned in the scope of 3.7 release
https://github.com/microsoft/TypeScript/issues/33352


You can try to write a custom function like that.

The main advantage of the approach is a type-checking and partial intellisense.

export function nullSafe<T, 
    K0 extends keyof T, 
    K1 extends keyof T[K0],
    K2 extends keyof T[K0][K1],
    K3 extends keyof T[K0][K1][K2],
    K4 extends keyof T[K0][K1][K2][K3],
    K5 extends keyof T[K0][K1][K2][K3][K4]>
    (obj: T, k0: K0, k1?: K1, k2?: K2, k3?: K3, k4?: K4, k5?: K5) {
    let result: any = obj;

    const keysCount = arguments.length - 1;
    for (var i = 1; i <= keysCount; i++) {
        if (result === null || result === undefined) return result;
        result = result[arguments[i]];
    }

    return result;
}

And usage (supports up to 5 parameters and can be extended):

nullSafe(a, 'b', 'c');

Example on playground.

NaN
  • 1,955
  • 13
  • 16
  • 1
    This works nice, but It has a problem - VSCode will not show this when using "Find All References" on one of the keys. – Ivan Koshelev May 08 '19 at 21:38
  • What kind of type checking ? The moment you pass strings in as params, you have lost the type. If so, you are better off to just use lodash.get – Val Neekman Jul 31 '19 at 16:08
17

Another alternative that uses an external library is _.has() from Lodash.

E.g.

_.has(a, 'b.c')

is equal to

(a && a.b && a.b.c)

EDIT: As noted in the comments, you lose out on Typescript's type inference when using this method. E.g. Assuming that one's objects are properly typed, one would get a compilation error with (a && a.b && a.b.z) if z is not defined as a field of object b. But using _.has(a, 'b.z'), one would not get that error.

TiaanM
  • 559
  • 4
  • 7
  • Nice tip, while no other solution is available, this seems to be a good alternative. – Dimas Crocco Dec 12 '17 at 16:43
  • 3
    But how does it work with TypeScript's type inference? – VitalyB Jan 22 '18 at 07:01
  • @VitalyB probably u mean importing corresponding interface of lodash. (import * as _ from 'lodash';) – Ankur Arora Mar 28 '19 at 08:33
  • 1
    @AnkurArora, I think what VitalyB means is that no type checking is done using the Lodash method. E.g. Assuming that one's objects are properly typed, one would get a compilation error with (a && a.b && a.b.z) if z is not defined as a field of object b. But using _.has(a, 'b.z'), one would not get that error. As for the import statement, yes, it is definitely needed. – TiaanM Mar 28 '19 at 14:37
  • 1
    @TiaanM That's exactly what I meant. I just found a new library, however, that covers the types issue: https://stackoverflow.com/a/55462483/126574 – VitalyB Apr 01 '19 at 19:46
4

A new library called ts-optchain provides this functionality, and unlike lodash' solution, it also keeps your types safe, here is a sample of how it is used (taken from the readme):

import { oc } from 'ts-optchain';

interface I {
  a?: string;
  b?: {
    d?: string;
  };
  c?: Array<{
    u?: {
      v?: number;
    };
  }>;
  e?: {
    f?: string;
    g?: () => string;
  };
}

const x: I = {
  a: 'hello',
  b: {
    d: 'world',
  },
  c: [{ u: { v: -100 } }, { u: { v: 200 } }, {}, { u: { v: -300 } }],
};

// Here are a few examples of deep object traversal using (a) optional chaining vs
// (b) logic expressions. Each of the following pairs are equivalent in
// result. Note how the benefits of optional chaining accrue with
// the depth and complexity of the traversal.

oc(x).a(); // 'hello'
x.a;

oc(x).b.d(); // 'world'
x.b && x.b.d;

oc(x).c[0].u.v(); // -100
x.c && x.c[0] && x.c[0].u && x.c[0].u.v;

oc(x).c[100].u.v(); // undefined
x.c && x.c[100] && x.c[100].u && x.c[100].u.v;

oc(x).c[100].u.v(1234); // 1234
(x.c && x.c[100] && x.c[100].u && x.c[100].u.v) || 1234;

oc(x).e.f(); // undefined
x.e && x.e.f;

oc(x).e.f('optional default value'); // 'optional default value'
(x.e && x.e.f) || 'optional default value';

// NOTE: working with function value types can be risky. Additional run-time
// checks to verify that object types are functions before invocation are advised!
oc(x).e.g(() => 'Yo Yo')(); // 'Yo Yo'
((x.e && x.e.g) || (() => 'Yo Yo'))();
VitalyB
  • 12,397
  • 9
  • 72
  • 94
2

Building on @Pvl's answer, you can include type safety on your returned value as well if you use overrides:

function dig<
  T,
  K1 extends keyof T
  >(obj: T, key1: K1): T[K1];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1]
  >(obj: T, key1: K1, key2: K2): T[K1][K2];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2]
  >(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4): T[K1][K2][K3][K4];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2: K2, key3: K3, key4: K4, key5: K5): T[K1][K2][K3][K4][K5];

function dig<
  T,
  K1 extends keyof T,
  K2 extends keyof T[K1],
  K3 extends keyof T[K1][K2],
  K4 extends keyof T[K1][K2][K3],
  K5 extends keyof T[K1][K2][K3][K4]
  >(obj: T, key1: K1, key2?: K2, key3?: K3, key4?: K4, key5?: K5):
  T[K1] |
  T[K1][K2] |
  T[K1][K2][K3] |
  T[K1][K2][K3][K4] |
  T[K1][K2][K3][K4][K5] {
    let value: any = obj && obj[key1];

    if (key2) {
      value = value && value[key2];
    }

    if (key3) {
      value = value && value[key3];
    }

    if (key4) {
      value = value && value[key4];
    }

    if (key5) {
      value = value && value[key5];
    }

    return value;
}

Example on playground.

Alec
  • 2,432
  • 2
  • 18
  • 28
1

Versions above typescript 3.7 supports safe navigation operator for typescript < 3.7 I made this function which can be useful.

export function isAccessible(data, keys, start=0) {
  if (start == 0 && (data == null || data == undefined)) {
    console.warn("data",data);
    return false;
  } else {
    if (data[keys[start]] == null || data[keys[start]] == undefined) {
      console.warn("Object valid till", keys.slice(0,start),keys[start],"undefined");
      return false;
    } else {
      if (start + 1 >= keys.length) {
        return data[keys[start]];
      }
      return this.isAccessible(data[keys[start]], keys, start + 1);
    }
  }
}

function call in code

Suppose we have a Object obj keys of which can vary and we want to check if obj.key1.key2 is accessible or not then function call will be as follows:

isAccessible(Object,["key1","key2"])
Nilesh Pal
  • 23
  • 7