In Typescript, what is the ! (exclamation mark / bang) operator?
This is technically called the non-null assertion operator. If the typescript compiler complains that a value could be null or undefined, you can use the ! operator to assert that the said value is NOT null or undefined.
TLDR
This is technically called the non-null assertion operator. If the typescript compiler complains about a value being null
or undefined
, you can use the !
operator to assert that the said value is not null
or undefined
.
Personal take: avoid doing this wherever possible.
What is the non-null assertion operator?
null
and undefined
are valid Javascript values.
The statement above holds true for all Typescript applications as well.
However, Typescript goes one step further.
null
and undefined
are equally valid types, e.g., consider the following:
// explicit null
let a: null
a = null
// the following assignments will yield errors
a= undefined
a = {}
// explicit undefined
let b: undefined
// the following assignments will yield errors
b = null
b = {}
In certain cases, the Typescript compiler cannot tell whether a certain value is defined or not, i.e., not null
or undefined
.
For example, assuming you had a value Foo
.
Foo!
produces a value of the type of Foo
with null
and undefined
excluded.
You essentially say to the Typescript compiler, I am sure, Foo
will NOT be null
or undefined
Let’s explore a naive example.
In standard Javascript, you may concatenate two strings with the .concat
method:
const str1 = "Hello"
const str2 = "World"
const greeting = str1.concat(' ', str2)
// Hello World
Write a simple duplicate string function that calls .concat
with itself as an argument:
function duplicate(text: string | null) {
return text.concat(text);
}
Note that the argument text
is typed as string | null
In strict mode, Typescript will complain here, as calling concat
with null
can lead to unexpected results.
The typescript error will read: Object is possibly 'null'.(2531)
On the flip side, a rather lazy way to silence the compiler error is to use the non-null assertion operator:
function duplicate(text: string | null) {
return text!.concat(text!);
}
Note the exclamation mark after the text
variable, i.e, text!
The text
type represents string | null
.
text!
represents just string
i.e., with null
or undefined
removed from the variable type.
The result? You’ve silenced the Typescript error.
However, this is a silly fix.
duplicate
can indeed be called with null
, which may lead to unexpected results.
Note that the following example also holds true if text
is an optional property:
// text could be "undefined"
function duplicate(text?: string) {
return text!.concat(text!);
}
Pitfalls (and what to do)
When working with Typescript as a new user, you may feel like you’re fighting a lost battle.
The errors don’t make sense to you.
Your goal is to remove the error and move on with your life as swiftly as you can.
However, you should be careful with using the non-null assertion operator.
Silencing a Typescript error doesn’t mean there may not still be an underlying issue—if unaddressed.
As you saw in the earlier example, you lose every relevant Typescript safety against wrong usages where null
and undefined
could be unwanted.
So, what should you do?
If you write React, consider an example you’re likely familiar with:
const MyComponent = () => {
const ref = React.createRef<HTMLInputElement>();
//compilation error: ref.current is possibly null
const goToInput = () => ref.current.scrollIntoView();
return (
<div>
<input ref={ref}/>
<button onClick={goToInput}>Go to Input</button>
</div>
);
};
In the example above (for those who do not write React), in the React
mental model, ref.current
will certainly be available at the time the button is clicked by the user.
The ref
object is set soon after the UI elements are rendered.
Typescript does not know this, and you may be forced to use the non-null assertion operator here.
Essentially, say to the Typescript compiler, I know what I’m doing, you don’t.
const goToInput = () => ref.current!.scrollIntoView();
Note the exclamation mark !
.
This “fixes” the error.
However, if in the future, someone removes the ref
from the input, and there were no automated tests to catch this, you now have a bug.
// before
<input ref={ref}/>
// after
<input />
Typescript will be unable to spot the error in the following line:
const goToInput = () => ref.current!.scrollIntoView();
By using the non-null assertion operator, the Typescript compiler will act as if null
and undefined
are never possible for the value in question. In this case, ref.current
.
Solution 1: find an alternative fix
The first line of action you should employ is to find an alternative fix.
For example, often you can explicitly check for null
and undefined
values.
// before
const goToInput = () => ref.current!.scrollIntoView();
// now
const goToInput = () => {
if (ref.current) {
//Typescript will understand that ref.current is certianly
//avaialble in this branch
ref.current.scrollIntoView()
}
};
// alternatively (use the logical AND operator)
const goToInput = () => ref.current && ref.current.scrollIntoView();
Numerous engineers will argue over the fact that this is more verbose.
That’s correct.
However, choose verbose over possibly breaking code being pushed to production.
This is a personal preference. Your mileage may differ.
Solution 2: explicitly throw an error
In cases where an alternative fix doesn’t cut it and non-null assertion operator seems like the only solution, I typically advise you throw an error before doing this.
Here’s an example (in pseudocode):
function doSomething (value) {
// for some reason TS thinks value could be
// null or undefined but you disagree
if(!value) {
// explicilty assert this is the case
// throw an error or log this somewhere you can trace
throw new Error('uexpected error: value not present')
}
// go ahead and use the non-null assertion operator
console.log(value)
}
A practical case where I’ve found myself sometimes doing this is while using Formik
.
Except things have changed, I do think Formik
is poorly typed in numerous instances.
The example may go similar to you've done your Formik validation and are sure that your values exist.
Here’s some pseudocode:
<Formik
validationSchema={...}
onSubmit={(values) => {
// you are sure values.name should exist because you had
// validated in validationSchema but Typescript doesn't know this
if(!values.name) {
throw new Error('Invalid form, name is required')
}
console.log(values.name!)
}}>
</Formik>
In the pseudocode above, values
could be typed as:
type Values = {
name?: string
}
But before you hit onSubmit
, you’ve added some validation to show a UI form error for the user to input a name
before moving on to the form submission.
There are other ways to get around this, but if you find yourself in such a case where you’re sure a value exists but can’t quite communicate that to the Typescript compiler, use the non-null assertion operator. But also add an assertion of your own by throwing an error you can trace.
How about an implicit assertion?
Even though the name of the operator reads non-null assertion operator, no “assertion” is actually being made.
You’re mostly asserting (as the developer), that the value exists.
The Typescript compiler does NOT assert that this value exists.
So, if you must, you may go ahead and add your assertion, e.g., as discussed in the earlier section.
Also, note that no more Javascript code is emitted by using the non-null assertion operator.
As stated earlier, there’s no assertion done here by Typescript.
Consequently, Typescript will not emit some code that checks if this value exists or not.
The Javascript code emitted will act as if this value always existed.
Conclusion
TypeScript 2.0 saw the release of the non-null assertion operator. Yes, it’s been around for some time (released in 2016). At the time of writing, the latest version of Typescript is v4.7
.
If the typescript compiler complains about a value being null
or undefined
, you can use the !
operator to assert that the said value is not null or undefined.
Only do this if you’re certain that is the case.
Even better, go ahead and add an assertion of your own, or try to find an alternative solution.
Some may argue that if you need to use the non-null assertion operator every time, it’s a sign you’re poorly representing the state of your application state via Typescript.
I agree with this school of thought.