Types

Introduction

We use TypeScript to add type checking to our code. In this lesson we will looking at how we can add types to our code and the different type examples.

Adding a type

In most cases, we typically add a data type with a colon : and then the data type afterwards.

Let’s have another look at the previous example we worked with.

typescript
	function addNumbers(a: number, b: number): number {
	  return a + b;
	}
	
	console.log(addNumbers(10, 20));

Our first parameter, a, can be seen to have the number type added to it.

typescript
	a: number;

TypeScript will now check that the parameter a is always a number. If we tried to pass a string as a then we would see that there is a TypeScript error: Argument of type 'string' is not assignable to parameter of type 'number'.ts(2345)

Incorrect type

Above: A type error being shown because the incorrect type was passed into the function.

Primitive types

The most commonly used primitives are number, string and boolean. You can use these as written for your types e.g.

typescript
	function exampleFunction(myNumber: number, myString: string, myBoolean: boolean) {
	  /* Code here */
	}

The any type

TypeScript has an any type which means that a value can be of any type.

We do not want to use the any type because it defeats the point of TypeScript. You should not be using the any type for applications.

With the above being said, it is fine to use the any type as a placeholder while you are still writing out your code, with the intention of correctly typing the code once you know what the type is.

Adding type annotations to variables and type inference

When we create a variable we can optionally add types to our variables.

In the example below we add a string to the name of our user:

typescript
	let firstName: string = 'Ola';

Type inference

Type inference is a fancy way of saying that TypeScript will automatically figure out the type of a variable based on the contents of that variable.

In the example above we specified a string type and then set the value to Ola.

TypeScript would have actually automatically typed firstName as a string because we initially set it to a string. This is type inference.

If we remove the string type and hover over the variable name, we will see that our type is set to string.

Type hover

Above: Seeing our type is set to string through type inference.

Arrays

We can type arrays by adding the type and then square brackets [] e.g. number[].

You could do this with any type e.g. string[], boolean[].

You can also type arrays by writing the Array keyword and then the type in angled brackets <> e.g. Array<number>.

Functions

There are two main aspects to typing a function.

Adding types to the:

  1. Parameters
  2. The return type

1. Typing the parameters

We should add type annotations to our parameters being passed to a function.

typescript
	function addNumbers(a: number, b: number) {
	  return a + b;
	}

2. Adding the return type

We should also add a return type to our functions. We add the return type with a colon : immediately after the parameters. In the example below, we are adding a string return type:

typescript
	function addNumbers(a: number, b: number): string {
	  return `The sum of the numbers is ${a + b}`;
	}

Objects

We can add types to the properties of our objects. In the example below, we add types to an object we are passing in.

typescript
	function greetPerson(person: { firstName: string; lastName: string }) {
	  return `Hello ${person.firstName} ${person.lastName}.`;
	}
	
	greetPerson({ firstName: 'Ola', lastName: 'Nordmann' });

NOTE: We typically use Type aliases and interfaces when dealing with objects which we will be looking at shortly.

Unions

We might want to combine types. For example, we might have an age variable where want the value to either be a string or a number. We can use unions to do this. A union is created through the use of separating types with the pipe | character.

In the example below we are creating a userAge variable which has either a number or a string type.

typescript
	let userAge: number | string;
	
	userAge = 10; // No error
	
	userAge = '10'; // No error
	
	userAge = true; // Error

Adding types reminds us to add the right safeguards to deal with different parameter types. In the example below we are parsing a so that we ensure we have mathematical addition instead of string concatenation.

typescript
	function addNumbers(a: string | number, b: number): number {
	  if (typeof a === 'string') {
	    return parseFloat(a) + b;
	  } else {
	    return a + b;
	  }
	}

Type aliases

A type alias allows us to specify a shape shape which we can then reuse in our code.

In the example below we create a Person type alias and then use that in a function.

typescript
	type Person = {
	  firstName: string;
	  lastName: string;
	  isAdmin: boolean;
	  age: number;
	};
	
	function greetPerson(person: Person) {
	  return `Hello ${person.firstName} ${person.lastName}!`;
	}

You will see that when we use the person parameter we get auto-completion, one of the great benefits of TypeScript:

Type auto-completion

Above: Type auto-completion.

Interfaces

Interfaces are similar to type aliases.

Below is the same code as the type alias however we have switched out the type alias for an interface.

typescript
	interface Person {
	  firstName: string;
	  lastName: string;
	  isAdmin: boolean;
	  age: number;
	}
	
	function greetPerson(person: Person) {
	  return `Hello ${person.firstName} ${person.lastName}!`;
	}

Differences between types aliases and interfaces

Type aliases and interfaces are the same for the most part.

The differences are:

1. Extending a type alias/interface

We can extend (grow) our type aliases and interfaces however they are done slightly differently.

In the example below we extend a type alias using an intersection.

typescript
	type Person = {
	  firstName: string;
	  lastName: string;
	};
	
	type Student = Person & {
	  marks: number[];
	};
	
	const teacher: Student = {
	  firstName: 'Ola',
	  lastName: 'Nordmann',
	  marks: [95, 97, 85]
	};

In the example below, we extend the interface using the extends keyword:

typescript
	interface Person {
	  firstName: string;
	  lastName: string;
	}
	
	interface Student extends Person {
	  marks: number[];
	}
	
	const teacher: Student = {
	  firstName: 'Ola',
	  lastName: 'Nordmann',
	  marks: [95, 97, 85]
	};

2. Changing a type alias/interface

We can’t change a type alias once it has been created.

The code below will display an error:

typescript
	type Person = {
	  firstName: string;
	  lastName: string;
	};
	
	type Person = {
	  title: string;
	};

For interfaces, we can change the interface without an error being shown:

typescript
	interface Person {
	  firstName: string;
	  lastName: string;
	}
	
	interface Person {
	  title: string;
	}

Optional types

We can create optional types by using a question ? mark. Adding an optional type means that the value does not have to be provided.

typescript
	type Person = {
	  firstName: string;
	  lastName: string;
	  isAdmin: boolean;
	  age: number;
	  myOptionalValue?: number;
	};

In the example above, we have added an extra property myOptionalValue and have made this optional. This means we will not have to pass through myOptionalValue when using the Person type.

Type assertions

Type assertions are for when we have values that TypeScript would not be able to infer.

An example of this is when we are getting an HTML element, such as an <input> element. TypeScript would not be able to infer that the element we are getting is an input as it is unaware of the HTML DOM. We can use a type assertion to let TypeScript know that the element is expected to be an <input>, which then leads to code suggestions based on an input element.

typescript
	const nameInput = document.getElementById('name-input') as HTMLInputElement;

Type Assertion example

Above: Type Assertion leading to auto-completion of code.

We can add type assertions in 2 ways:

  1. Adding your type in angled brackets next to the variable name:

  2. Using the as keyword.

Consider the following example where we look at the two types of type assertions:

typescript
	let value: any = 10;
	
	// Example 1: angled brackets
	let formattedPrice1 = <number>value.toFixed(2);
	
	// Example 2: "as" keyword
	let formattedPrice2 = (value as number).toFixed(2);

Literal types

Literal types allow us to specify exact values in our types. We can use unions in our literal types to ensure that only specific values are being used.

In the example below we are creating a firstName variable that only accepts Ola and Kari as values. Trying to set firstName to John will display an error:

typescript
	let firstName: 'Ola' | 'Kari' = 'Ola';
	
	firstName = 'Ola';
	firstName = 'Kari';
	firstName = 'John';

Literal type example

Above: The wrong name being used in our literal type shows an error.

Enums

Enums allow us to specify a set of values to a type so that only these values can be selected.

In the example below we have created a Fruit enum with a list of fruits. Only the values we have listed can be selected:

typescript
	enum Fruit {
	  Apple,
	  Banana,
	  Orange
	}

Enums fruit example

Above: An enum example showing us code suggestions.

Something to note is that the enums will evaluate to indexes, starting with 0 i.e.

typescript
	enum Fruit {
	  Apple, // Value is 0
	  Banana, // Value is 1
	  Orange // Value is 2
	}

We could instead specify strings for our enums:

typescript
	enum Fruit {
	  Apple = 'APPLE', // Value is "APPLE"
	  Banana = 'BANANA', // Value is "BANANA"
	  Orange = 'ORANGE' // Value is "ORANGE"
	}

When we use these enums we will now instead have the strings being the result.

NOTE: Enums are not natively present in JavaScript and are added by TypeScript, as seen in the image below where an enum is converted to an object after compilation. Using enums over string literals might not be preferred by some workplaces which is important to keep in mind.


Lesson task

Goal

For the student to demonstrate they can use TypeScript types.

Brief

Follow the Level 1 Process as instructed.

NOTE: Lesson tasks do not get submitted on Moodle and are not assessed by tutors. They are mainly there for you to practise what you have learned in the lesson.

Level 1 process

  1. Add an interface, called Game or IGame, that contains the correct types based on the information given:
txt
	id: number
	title: string
	isMultiplayer: boolean
	yearReleased: number
	numberOfPlayers: number, optional
	genre: string
  1. Create an enum called Genre that has the following fields game fields:
txt
	Adventure
	Action
	RPG
	Sports

Replace the genre field in your interface width the enum you have created.

  1. Create a createGame function that returns an object that matches the shape of the interface you have created. The return type must be the interface you have created. Make sure to pass in the right parameters and types.

Additional resources

TypeScript docs: Everyday Types