Reputation: 2885
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) { }
Upvotes: 218
Views: 286545
Reputation: 31
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"])
Upvotes: 0
Reputation: 5228
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
Upvotes: 164
Reputation: 37918
!
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 ofa
withnull
andundefined
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
orundefined
), the expression short-circuits with a return value ofundefined
. When used with function calls, it returnsundefined
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
Upvotes: 247
Reputation: 1985
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.
Upvotes: 23
Reputation: 12855
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'))();
Upvotes: 4
Reputation: 569
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.
Upvotes: 18
Reputation: 2752
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.
Upvotes: 2