I was working with one of the developer's on my team and he asked what the question mark meant in an Angular view template. I knew what it did, and have used it routinely, but didn't know it had a formal name. It is called a Safe Navigation Operator. It let's Angular process an expression without error, even if some object in the dot notation chain is invalid.

This will make more sense with a sample.

The Setup

First create a brand new Angular project. For the purposes of this sample, I'm going to create everything in the main app.component.

Let's create two classes, one nested inside the other. The first is a class named InnerData:

view plain print about
1class InnerData {
2 public data = 'default';

The second will be called OuterData:

view plain print about
1class OuterData {
2 public inner: InnerData;

Notice that the InnerData property on the OuterData class is not defaulted, so it could be in a null state.

Let's create a few instances of the OuterData :

view plain print about
1public populatedInstance: OuterData;
2 public nullInstance: OuterData;
3 public nullInner: OuterData;
4 public nullInnerData: OuterData;

In the constructor let's populate a few of these variables. The nullInstance value will not be defaulted because we'll leave the Null. Beyond that, let's start with the populatedInstance:

view plain print about
1this.populatedInstance = new OuterData();
2 this.populatedInstance.inner = new InnerData();
3 this.populatedInstance.inner.data = 'name';

That's has all the data we expect, so when we try to access the data value in a view we won't have any issues.

Created the null inner:

view plain print about
1this.nullInner = new OuterData();

This gives us an OuterData instance, but the InnerData value will be left undefined, so if we try to drill down into `nullInner.inner.data` we'll get an error.

view plain print about
1this.nullInner = new OuterData();

Now, let's go ahead and populate the null inner data:

view plain print about
1this.nullInnerData = new OuterData();
2 this.nullInnerData.inner = new InnerData();
3 delete this.nullInnerData.inner.data;

Notice we defaulted the data value on the InnerData object, so this purposely removes that object so it is non existent.

Using The Save Navigation Operator

Now switch over the app.component.html file. If we try to display the data item from the populated instance, it goes like this:

view plain print about

This works great. Now try to access the same value using the nullInner object:

view plain print about
1<h1>Null Inner</h1>

Oops, something went wrong:

You'll see a console error that says cannot access data undefined. The Safe Navigation operator exists to fix this:

view plain print about
1<h1>Null Inner</h1>

Angular process the question mark and knows not to continue traversing down the object.property path if nullInner.inner is undefined. Our UI will display an empty string.

Let's look at our next object, the one with null inner data. We modified this object to delete the data property on the inner object instance.

Output it:

view plain print about
1<h1>null Inner Data</h1>

This works without error, but nothing is displayed in the UI. This surprised me a bit, because I expected an error. It looks like under the hood Angular is transforming the undefined data property into an empty string.

Finally, output the null instance. This is the one we never defined:

view plain print about
1<h1>null Instance</h1>

This will show an error:

Since nullInstance is undefined, the inner variable cannot be accessed.

We can find that using the safe navigation operator:

view plain print about
1<h1>null Instance</h1>

Rerun the code and see everything error free.

The places where the value doesn't exist will return an empty value, but at least you don't get errors. I'm more likely to use this in structural directives such as an *ngIf, because this:

view plain print about
1<div *ngIf="nullInner && nullInner.inner && nullInner.inner.data"></div>

Is not as nice to read as this:

view plain print about
1<div *ngIf="nullInner?.inner?.data"></div>

Final Thoughts

Go play with the code here!

It is cool that Angular implemented this feature and it has helped me simple code in my templates. An update of TypeScript implements this same feature directly, named optional chaining. I'm going to write about that next week.