Narrowing with else and Type Guards

Learn via video courses
Topics Covered

Overview

Typescript is Javascript code with Strictly Typed Language. Javascript runs on template-based code snippets, one may build objects without defining classes by using Object Literals and function Object() methods. An object is an instance that includes a collection of key-value pairs. The values can be scalars, functions, or arrays of other objects.

Introduction

Type narrowing is exactly what it sounds like—it involves reducing a broad type to a more focused one. You have undoubtedly come across this if you have ever worked with union types, such as string and number. In fact, since this typing is equal to x: number | undefined, optional types like x?: number frequently call for narrowing in typescript as well. You'll probably need to handle each instance in your code in each of these scenarios, so you'll need to first narrow down the type.

When working with a variable that has numerous possible types, such as an unknown or a union type, type narrowing in typescript can be used to 'narrow' the variable down to a single type. Because the TypeScript compiler comprehends the context of our code and ensures that this narrowing in typescript occurs in a fully type-safe manner, we collaborate with it.

Consider the case where a function's parameter is of the type Date | undefined. The variable's type can only ever be either Date or undefined whilst the function is running; it cannot have both kinds at once.

The value within the if the condition is only treated as a string by TypeScript if we apply an if condition, verifying if the variable is not undefined. This form of narrowing in typescript exists.

Another method that is quite similar is known as type assertion. At first glance, it appears to be the simpler choice. By manually setting the type, we replace the compiler.

The compiler would say something along the lines of: 'OK, you know what you are doing there, but don't blame me if something goes wrong,' if it could talk to humans.

Therefore, type assertion should be avoided, and type narrowing in typescript should always be preferred.

What is Narrowing in TypeScript?

In essence, narrowing in typescript is the removal of types from a union. As you create code, it occurs frequently, especially if you use —strictNullChecks. You must first comprehend the distinction between "declared type" and "computed type" in order to comprehend narrowing in typescript.

If it mostly seems like boring JavaScript code, this is the objective. With the exception of the annotations we added, this TypeScript code resembles JavaScript. The goal of TypeScript's type system is to make writing normal JavaScript code as simple as feasible without having to go above and beyond to achieve type safety.

Even while it might not seem like much, there is a lot going on here. Similar to how TypeScript analyses runtime values using static types, JavaScript's runtime control flow structures, such as if/else, conditional ternaries, loops, truthiness tests, etc., may all have an impact on those types. Type analysis is thus superimposed on these constructs.

TypeScript acknowledges typeof padding === "number" within our if check as a specific type of code known as a sort guard. To determine the most precise type of value at a particular point, TypeScript analyses potential courses of execution that our program may take. The process of limiting types to more specialized types than stated takes into account these unique checks (referred to as type guards) and assignments. We can see these kinds evolve in numerous editors, and we'll even do it in our examples.

Truthiness Narrowing in Typescript

Two new English words were coined using JavaScript. Both false and true. A condition will return false if we enter a false value and true if we enter a truthy value. To make sense of their conditions, JavaScript constructs like if "coerce" them to booleans first. Then, based on whether the outcome is true or false, they pick which branches to take. Values such as

  • undefined
  • false
  • 0
  • 0n
  • "
  • null
  • 0n

Other values are forced to true while all coerce to false. By passing data via the Boolean function or the quicker double-Boolean negation, you can always convert values to booleans. TypeScript infers a narrow literal boolean type true for the latter, whereas it infers the first as a type boolean.

That might work in our favour. It would be true if the value is not undefined or null if we merely placed the input into the if condition. This is pretty comparable to the equalness operator in our situation, it's simply shorter.

Here's a minor detail we missed. The value would likewise be false if it were an empty string, and we would not be inside the if-condition. However, as long as we are aware of it, it is OK for our use case.

Let's add some interest to our example. We now include a string as a fourth type that might exist. The typeof now joins the game at this point.

First, we use truthiness narrowing in typescript to exclude the chance that the value is null or undefined and return 0.

The sole choice then is between string and date. Here, typeof may be used to determine whether the input is a string. If not, Date is the sole option. Perfect!

Equality Narrowing in Typescript

Checking to see if the input contains the values null and undefined would be a logical place to start. It can only be a Date if such is the case.

When this condition is added, TypeScript recognizes it and considers the input inside the if-block as a date, allowing us to securely use the agediff function.

Our first type guard, ‘Equality Narrowing’, is already a part of TypeScript's "implicit understanding."

Please note that the if condition does not do a direct check for the type null or undefined. It opposes the value. In a similar vein, we would be unable to construct the type check value Date: value === Date. Date is the input type, not the value.

Why are Undefined and Null Acceptable?

The solution is fairly clear. The only possible value for the type undefined is 'undefined.' The same holds true for null as well. The only possible value for a type of null is 'null.'

Therefore, what our condition actually accomplishes is to rule out any conceivable values that may be undefined or null.

The In Operator Narrowing in Typescript

The in operator in JavaScript may be used to check whether an object contains a property with a name. This is taken into consideration by TypeScript as a technique to limit the possible kinds.

As an illustration, consider the following line of code: 'value' in x. 'value' is a string literal, and x is a union type. The true branch limits the kinds of x to those that have either a necessary or optional property value, whereas the false branch limits the types to those that have a missing or optional property value.

Instanceof Narrowing in Typescript

When dealing with classes, type narrowing in typescript is also an option. Add a class called Person with a birthdate attribute of type Date to serve this function.

Since input might be of type Date or Details, it is obvious that the last return in the else clause would fail. The good news is that we can use instanceof since both are truly class instances.

If a value is an instance of a specific class, instanceof returns true. Therefore, if we include a condition that checks for the class Date, we are once more type-safe:

Be cautious that the whole class inheritance chain returns true for instanceof. So instanceof Entity would likewise return true if Person extended from class Entity.

Type Guards

A Type Guard is a type of code that aids in narrowing the type of variables inside a conditional block, such as if ... else if ... else statement or switch.

It does a runtime check to ensure the type in a scope.

I fully appreciate that the above sentences may appear incomprehensible at this stage, especially to less experienced programmers, so let's look at the TypeScript code:

The Price function converts the supplied value, which can be an integer or a string, to the 00.00 format, such that if the supplied value is 100, the output is 100.00.

It does, however, include an issue, which TypeScript cautions us about: property 'Fixed' does not exist on type 'string,' implying that the Fixed() function may only be called on number types.

Sure, you can prevent accepting type string, but in some circumstances, it's not clear which kind of data is arriving, therefore it's best to handle both to be safe.

Hovering over the Value variable in various points throughout the function reveals that the variable is of a different type. TypeScript is intelligent enough to recognize that the variable included within the if statement can only be of the string type.

Furthermore, it recognizes that the variable is of the integer type outside of the if statement. This is when we encountered the first Type Guard.

Type Guards in Typescript

In a conditional block, a type guard is a TypeScript technique used to determine the type of a variable. Regular functions called type guards take a type and return a boolean that indicates whether or not TypeScript can limit it down to a more precise type. Depending on the returned boolean, type guards have the only ability to guarantee that the value being checked is of a specific type.

The in operator and other built-in JavaScript operators, such as typeof and instanceof, are used by TypeScript to check if an object includes a property. By instructing the TypeScript compiler to infer a given type for a variable in a specific context using type guards, you can make sure that an argument's type matches what you claim it is.

Similar to feature detection, type guards are frequently used to limit a type and let you identify the right prototypes, methods, and attributes of a value. As a result, handling that value will be simple for you.

There are five main applications for type guards:

  • The typeof keyword
  • The in keyword
  • The instanceof keyword
  • Custom type guard with predicate
  • Equality narrowing type guard

The Typeof Type Guard

The type of a variable is established using the typeof type guard. The sort of guard is allegedly quite shallow and constrained. It can only identify the kinds that JavaScript recognizes:

  • string
  • boolean
  • symbol
  • bigint
  • function
  • number
  • undefined

The typeof type guard just returns objects for everything not on this list.

There are two different methods to write the typeof type guard:

or

The sample might be a boolean, symbol, integer, or string.

Student1 has a string | number type union parameter entry in the example below. We can see that Student is printed if the variable is a string, and RollNo is written if it is a number. We can retrieve the type from x with the typeof type guard:

The In Type Guard

The in-type guard uses that attribute to distinguish between various types by determining if an item possesses it. A boolean is often returned, indicating if the attribute is present in that object. It is used to check for browser support and for its narrowing capabilities.

The in-type guard's fundamental syntax is as follows: property in object

The in-type guard in the example below determines if the property car is present. If it does exist, the boolean value true is returned; if it does not, the value false is returned.

The Instanceof Type Guard

A built-in type guard called instanceof may be used to determine whether a value is an instance of a certain Object() function or class. To ascertain the type of an instance type, we may use this type guard to verify whether an object or value is derived from a class.

Below is the instanceof type guard's fundamental syntax:

We can see an instance of type guard in the example below:

Since both Tyres and Battery implement the Car interface, the above-mentioned getRandomCar method either returns a Tyres or Battery object. Tires and batteries each have unique function Object() signatures, and the instanceof type guard compares both of them to successfully identify the type.

Custom Type Guard with Predicate

The least effective method of employing type guards is normally to create a custom type guard. There are no restrictions on what may be checked when you write your own custom-type guard. However, if the custom type guard is written improperly, several mistakes may result. Therefore, accuracy is crucial.

Below is a picture of a custom-type guard:

The type predicate b is Tyres in the code above will cause TypeScript to reduce the type to Tyres rather than merely returning a boolean value.

Equality Narrowing Type Guard

The equality narrowing verifies an expression's value. Two variables must be of the same type in order for them to be equal. If a variable's type is unknown but it is equivalent to another variable with a known type, Typescript will use the data the well-known variable gives to restrict the type of the unknown variable:

Explanation

Both variables must be of the same type if variable x and variable y are equal. Typescript restricts it to a string in this case. It is still unclear what type x is without concentrating because it might be either a number or a string.

Conclusion

  • The two main types of narrowing are assertions are used in the former to change the type, and intersection types are used in the latter to add information.
  • TypeScript type guards are useful for guaranteeing a type's value and enhancing overall code flow.
  • The typeof type guard, instanceof type guard, or in type guard may typically handle your use case; but, if it is absolutely necessary, you might utilize a custom type guard.
  • When faced with a large number of types and the necessity to execute code that differs for each type, type guards are a very helpful tool for reducing the types.
  • We learned several methods for handling union types and how to reduce them to a single type. Because TypeScript can validate these patterns, we are not attempting to trick the compiler but rather to write code that is as type-safe as possible.
  • Type assertion should never be preferred above type narrowing.