Use of the Exclamation Mark in TypeScript

Jollen Moyani - Dec 12 '22 - - Dev Community

The exclamation mark in TypeScript

In many scripting languages, developers use the exclamation mark as a not operator. But when working with TypeScript, the exclamation mark acts as a non-null assertion operator. This non-null assertion will remove null and undefined values.

In this article, I will discuss several uses of the exclamation mark in TypeScript.

How does the exclamation mark behave in TypeScript?

Let’s see some simple examples of how the exclamation mark is used in TypeScript.

When defining a variable

If we define a string-type variable as string | null , it means that the variable holds a string or null value. But suppose we define a function that accepts only a string type as a parameter. In that case, the TypeScript compiler will reject our variable value since there is a possibility that it will have a null value. Refer to the following code:

let stringWord: string | null

const number =1
if(number){
  stringWord = Test word
}
console.log(stringWord.toUpperCase())

Enter fullscreen mode Exit fullscreen mode

In this case, the following error will appear:

Error: Object is possibly ‘null’.ts(2531)
Enter fullscreen mode Exit fullscreen mode

However, if you use the non-null assertion operator, you can convey to the TypeScript compiler that the stringWord variable is never null or undefined.

This is what the modified code looks like:

let stringWord: string | null

const number =1
if(number){
  stringWord = Test word
}
console.log(stringWord!.toUpperCase()) // added the exclamation mark
Enter fullscreen mode Exit fullscreen mode

When passing a value as an optional argument in a function

Consider a function we use to get a student’s details. The function will accept the parameter studentName.

Function getDetails(studentName ?: string){
  const name: string = studentName
  const age: number = 25
  console.log(Name : ${name})
  console.log(Age: ${age})
}
Enter fullscreen mode Exit fullscreen mode

In the above code example, you can see the use of ? after the parameter. In TypeScript, the question mark is used to define an argument as optional. It is the same as specifying the type as undefined. It is similar to studentName: string | undefined.

The above function will throw the following error.

Error: Type ‘string | undefined’ is not assignable to type ‘string’. 
Type ‘undefined’ is not assignable to type ‘string’.
Enter fullscreen mode Exit fullscreen mode

The reason for this error is that the compiler considers studentName as a null value or undefined value. We can solve this issue by adding an exclamation mark after the studentName and making it a string type.

Function getDetails(studentName ?: string){
  const name: string = studentName!
  const age: number = 25
  console.log(Name : ${name})
  console.log(Age: ${age})
}
Enter fullscreen mode Exit fullscreen mode

When getting the attribute of an optional object within a function

Here, we define a type Student and the function getName that accepts an argument of type Student.

type Student ={
  sid : number;
  name: string;
};

function getName(std ?: Student){
  console.log("Name of the student is ${std.name}")
}
Enter fullscreen mode Exit fullscreen mode

In this function, the std parameter is marked as an optional parameter, so we cannot safely access the property of std. It can be either a Student type or an undefined type. If we don’t use the non-null assertion operator, we will get the following error:

Error: Object is possibly 'undefined'. ts(2532)
Enter fullscreen mode Exit fullscreen mode

To avoid this error, inform the compiler that this variable will never be undefined or null. Introducing the non-null assertion to this code will solve this error.

type Student ={
  sid : number;
  name: string;
};

function getName(std ?: Student){
  console.log("Name of the Student is ${std!.name}");
}
Enter fullscreen mode Exit fullscreen mode

Use cases for the exclamation mark as a non-null assertion operator

Let’s discuss some use cases for the exclamation mark as a non-null assertion in TypeScript.

Search for an item that exists in a list

Let’s consider a scenario where specific items exist in a list and you need to access those elements and check them.

interface Student {
     sid : number;
     name: string;
};

const students: Student[] =[
   {
      sid : 1,
      name: "Alex",
   },
   {
      sid : 2,
      name: "Gerome",
   },
   {
      sid : 3,
      name: "Bennet",
   },
 ];

 const getStudent =( sid: number) => {
      return students.find((students) => students.sid ==sid);
 };

 const students = getStudent(1);

Enter fullscreen mode Exit fullscreen mode

In the above code example, we have a list of students, and we need to find the student details based on the student ID, sid. Everything looks fine in the code but imagine if the type of the variable students can be undefined. In this case, we cannot pass sid as an argument.

Most of the time, we feel confident that these arrays have only defined values when we perform a search on them. In practice, though, we should prepare the application for handling null or undefined cases too. This can be easily achieved using the ! operator.

The following code snippet shows how to address this case using the non-null assertion operator.

const getStudent =( sid: number) => {
   return students.find((students) => students.sid ==sid)!;
   //Add the exclamation mark
};

const students = getStudent(1);
Enter fullscreen mode Exit fullscreen mode

Handling React refs in TypeScript

React refs provide a way to access DOM nodes or React elements. When we use React refs, we have to access the current attribute, ref.current.

This attribute is in a null state until the rendering happens, or it can take the following type:

HTMLDivElement | null
Enter fullscreen mode Exit fullscreen mode

Let’s explore the way to handle this null using an example.

// some code

const App = () => {
    const ref = useRef(null);

    const handleClick = () => {
      if(ref.current) {
        console.log(ref.current.getBoundingClientRect());
      }
    };

    return (
      <div className="App" ref={ref}>
        {/* ... */}
        <button onClick={handleClick}>OK</button>
      </div>
    ); 
};
Enter fullscreen mode Exit fullscreen mode

Here, we created a component with access to the div element with the App class DOM node. Clicking on the Ok button will display the size of the component and its position in the viewport.

There are times when the UI element will not be rendered when a click action is performed on the OK button. This might lead to an exception during runtime. The ! operator can be used to eliminate this pointless check. Refer to the following code example:

const handleClick = () => {
  if(ref.current) {
     console.log(ref.current!.getBoundingClientRect());
  }
};
Enter fullscreen mode Exit fullscreen mode

When is the exclamation mark assertion not useful?

Even though we added the ! operator to handle null and undefined issues; this operator does not change any runtime behavior of your code. Errors can still disrupt the execution even if you handle the null and undefined issues with the non-null assertion operator.

Unlike JavaScript, TypeScript uses assertions for types. JavaScript variables can handle type issues during execution time. So, handling type issues depends on the scenario developers face when dealing with such problems.

The ! operator is TypeScript’s main advantage for preventing runtime errors. Therefore, we need to focus more on adding additional checks to handle non-null issues since the ! operator is not the best practice for handling such matters.

Alternatives to the exclamation mark operator in TypeScript

You can use a few other options you can use as an alternative to the ! operator in TypeScript.

Type predicates

In TypeScript, type predicates define a function and perform a Boolean test which returns a type predicate. It will handle the null and undefined issues beforehand.

interface Student {
  sid : number;
  name: string;
};

function validateStudent(std?: Student) std is Student{
 return !! std
}
Enter fullscreen mode Exit fullscreen mode

We have to add the type predicate as shown in the above code sample. After that, we can perform the validation before the core functions in our code.

function getName(std ?: Student){
  if (!validateStudent(std)) {

     console.log('Student is invalid');
     return
  }
  console.log("Name of the Student is ${std.name}");
}
Enter fullscreen mode Exit fullscreen mode

Optional chaining

Optional chaining changes the reference value defaults to undefined even if the variable is undefined or null. Let’s see the following example:

interface Student {
  sid : number;
  name: string;
};

function getName(std ?: Student): void {
  console.log("Name of the Student is ", std?.name);
}
Enter fullscreen mode Exit fullscreen mode

If the std is undefined, it will display the following output:

Name of the Student is undefined
Enter fullscreen mode Exit fullscreen mode

Conclusion

TypeScript’s superpower is its type safety. However, in some cases, we have to disable the strict type checks. If you want to add more flexible code components to your code, you can use the non-null assertion-type operations covered in this blog.

Even though ! is a helpful operator, developers must be careful when using it, or they may get stuck with unknown runtime errors. But if you like your code less lengthy and with fewer validations, you are welcome to use the exclamation mark operator.

Thank you for reading!

Syncfusion’s Essential JS 2 is the only suite you will need to build an app. It contains over 65 high-performance, lightweight, modular, and responsive UI components in a single package. Download a free trial to evaluate the controls today.

If you have questions or comments, contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!

Related blogs

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .