TypeScript has many advanced type capabilities and which make writing dynamically typed code easy. It also facilitates the adoption of existing JavaScript code since it lets us keep the dynamic capabilities of JavaScript while using the type-checking capability of TypeScript. There are multiple kinds of advanced types in TypeScript, like intersection types, union types, type guards, nullable types, and type aliases, and more. In this article, we look at nullable types and type aliases.
Nullable Types
To let us assign undefined
to a property with the --strictNullChecks
flag on, TypeScript supports nullable types. With the flag on, we can’t assign undefined
to type members that don’t have the nullable operator attached to it. To use it, we just put a question mark after the member name for the type we want to use.
If we have the strictNullChecks
flag on and we set a value of a property to null
or undefined
, then like we do in the following code:
interface Person {
name: string;
age: number;
}
let p: Person = {
name: 'Jane',
age: null
}
Then we get the following errors:
Type 'null' is not assignable to type 'number'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'
The errors above won’t appear if we have strictNullChecks
off and the TypeScript compiler will allow the code to be compiled.
If we have the strictNullChecks
flag on and we want to be able to set undefined
to a property as the value, then we can make the property nullable. For example, we can set a member of an interface to be nullable with the following code:
interface Person {
name: string;
age?: number;
}
let p: Person = {
name: 'Jane',
age: undefined
}
In the code above, we added a question mark after the age
member in the Person
interface to make it nullable. Then when we define the object, we can set age
to undefined
. We can’t still set age
to null
. If we try to do that, we get:
Type 'null' is not assignable to type 'number | undefined'.(2322)input.ts(3, 3): The expected type comes from property 'age' which is declared here on type 'Person'
As we can see, a nullable type is just a union type between the type that we declared and the undefined
type. This also means that we can use type guards with it like any other union type. For example, if we want to only get the age if it’s defined, we can write the following code:
const getAge = (age?: number) => {
if (age === undefined) {
return 0
}
else {
return age.toString();
}
}
In the getAge
function, we first check if the age
parameter is undefined
. If it is, then we return 0. Otherwise, we can call the toString()
method on it, which is available to number objects.
Likewise, we can eliminate null
values with a similar kind of code, for instance, we can write:
const getAge = (age?: number | null) => {
if (age === null) {
return 0
}
else if (age === undefined) {
return 0
}
else {
return age.toString();
}
}
This comes in handy because nullable types exclude null
from being assigned with strictNullChecks
on, so if we want null
to be able to be passed in as a value for the age
parameter, then we need to add null
to the union type. We can also combine the first 2 if
blocks into one:
const getAge = (age?: number | null) => {
if (age === null || age === undefined) {
return 0
}
else {
return age.toString();
}
}
Type Aliases
If we want to create a new name for an existing type, we can add a type alias to the type. This can be used for many types, including primitives, unions, tuples, and any other type that we can write by hand. To create a type alias, we can use the type
keyword to do so. For example, if we want to add an alias to a union type, we can write the following code:
interface Person {
name: string;
age: number;
}
interface Employee {
employeeCode: number;
}
type Laborer = Person & Employee;
let laborer: Laborer = {
name: 'Joe',
age: 20,
employeeCode: 100
}
The declaration of laborer
is the same as using the intersection type directly to type the laborer
object, as we do below:
let laborer: Person & Employee = {
name: 'Joe',
age: 20,
employeeCode: 100
}
We can declare type alias for primitive types like we any other kinds of types. For example, we can make a union type with different primitive types as we do in the following code:
type PossiblyNumber = number | string | null | undefined;
let x: PossiblyNumber = 2;
let y: PossiblyNumber = '2';
let a: PossiblyNumber = null;
let b: PossiblyNumber = undefined;
In the code above, the PossiblyNumber
type can be a number, string, null
or undefined
. If we try to assign an invalid to it like a boolean as in the following code:
let c: PossiblyNumber = false;
We get the following error:
Type 'false' is not assignable to type 'PossiblyNumber'.(2322)
just like any other invalid value assignment.
We can also include generic type markers in our type aliases. For example, we can write:
type Foo<T> = { value: T };
Generic type aliases can also be referenced in the properties of a type. For example, we can write:
type Tree<T> = {
value: T;
left: Tree<T>;
center: Tree<T>;
right: Tree<T>;
}
Then we can use the Tree
type as we do in the following code:
type Tree<T> = {
value: T,
left: Tree<T>;
center: Tree<T>;
right: Tree<T>;
}
let tree: Tree<string> = {} as Tree<string>;
tree.value = 'Jane';tree.left = {} as Tree<string>
tree.left.value = 'Joe';
tree.left.left = {} as Tree<string>;
tree.left.left.value = 'Amy';
tree.left.right = {} as Tree<string>
tree.left.right.value = 'James';tree.center = {} as Tree<string>
tree.center.value = 'Joe';tree.right = {} as Tree<string>
tree.right.value = 'Joe';console.log(tree);
The console.log
for tree
on the last line should get us:
{
"value": "Jane",
"left": {
"value": "Joe",
"left": {
"value": "Amy"
},
"right": {
"value": "James"
}
},
"center": {
"value": "Joe"
},
"right": {
"value": "Joe"
}
}
Nullable types are useful is we want to be able to assign undefined
to a property when strictNullChecks
flag is on when in our TypeScript compiler configuration. It’s simply a union type between whatever type you have and undefined
. It’s denoted by a question mark after the property name. This means we can use type guards with it like any other union type. Note that nullable types don’t allow null
values to be assigned to it since nullable types are only needed when strictNullChecks
flag is on. Type alias let us create a new name for types that we already have. We can also use generics with type alias, but we can’t use them as standalone types.