The non-null assertion operator, or that weird exclamation mark you might be seeing in TypeScript is an operator that tells TypeScript that a value won’t be null or undefined. In a technical sense, it eliminates null and undefined from the type of your variable. In this article I’ll cover the operator, how to use it, and why maybe not to.
Here’s what it looks like:
function getName(): string | null {
// ...
}
function greet(name: string) {
console.log(`Hi ${name}`);
}
const myName = getName(); //string | null
greet(myName); //Argument of type 'string | null' is not assignable to parameter of type 'string'.
//Type 'null' is not assignable to type 'string'.
greet(myName!) //✅t
Add an exclamation mark onto the end of a variable, and the result is that variable with any null or undefined eliminated from its type.
Should you use it?
You should be aware of the fact that the operator isn’t doing anything to the variable itself, just forcing TypeScript to believe it will exist. Let’s take our previous example, and fill in our function.
function getName() {
return Math.random() > 0.5 ? 'Omari' : null;
}
No need to add a return type anymore, TypeScript knows the function will always return a string or null.
The rest of the code still works:
function greet(name: string) {
console.log(`Hi ${name}`);
}
const myName = getName(); //string | null
greet(myName!); //✅
Even though we know something is wrong — “myName” will be null about half the time.
This is a simplified example, but the point is that you should be aware of why TypeScript thinks your variable might be null or undefined in the first place, and whether it’s worth risking the chance that it is. In this case, our code will still run, we’ll just get “Hi null”, so not too bad, but there are some alternatives.
What to use instead
The easiest solution to handle if your variable is null or undefined is to use type guards. Null and undefined are falsy, which makes it easy to check in an if statement:
if (myName) greet(myName);
This will include all falsy values, for example the empty string. In this case that’s helpful — we probably wouldn’t want to greet an empty name either, but you can also explicitly check for null or undefined with equality:
if (myName !== null) greet(myName);
Another way to check for null or undefined is to use the nullish coalescing operator (??), — introduced in TypeScript 3.7. This operator returns the left-hand side of the expression if it’s not null or undefined, and the right-hand side otherwise. It’s useful for giving variables default values in the case that they are null or undefined:
const myName = getName() ?? 'Omari'; //string | null greet(myName);
For not a lot more code, we’re back in alignment with TypeScript.
When might you use it?
One case I have seen the non-null assertion operator used is maps. Imagine if we wanted to use a map to count how many times we’ve greeted someone:
function greetWithCount(name: string) {
greet(name);
if (greetings.has(name)) {
const count = greetings.get(name);
//const count: number | undefined
greetings.set(name, count + 1);
}
}
You might be able to safely assume that inside the if statement, “.get()” should always return a value, since we’ve checked if that key has an entry. .get() always returns the type union “type | undefined” though. In this case, a quick non-null assertion is an easy solution. Again you could just check if “count” is undefined, but assuming the value exists isn’t the worst assumption in this case.
Conclusion
Thanks for reading! Hopefully, this article was a good introduction to that mysterious exclamation mark you’ve been seeing in TypeScript. Personally, I nearly always prefer handling null or undefined in other ways than the non-null assertion operator, but just like everything in coding, the choice is up to you!
Hey, I’m Omari! I’m a full-stack developer from the UK. I’m currently looking for graduate and freelance software engineering roles, so if you liked this article, check out my website.
Originally published at https://www.omarileon.me.