logo

TypeScript

How to use this page

If you're interested in a specific topic, you can just jump into it. If you want to learn TypeScript from scratch, start reading from the beginning. You can use your editor to test your code. A good understanding of JavaScript before learning TypeScript is highly recommended. To see the results of the functions and have more information, you can read the comments in the examples. If you want to learn TypeScript with React Native, check the React Native with TypeScript section.

TypeScript

TypeScript adds additional syntax to JavaScript to support a tighter integration with your editor.

Installation

To install TypeScript, run the command below:

npm install -g typescript

If you fail to install TypeScript, you may need to run the command above using the sudo command.

To check if the TypeScript installation is successful:

tsc -v

If TypeScript is installed successfully, you should see the TypeScript version.

You can create a TypeScript file with a "ts" extension. Let's create a file named "index.ts".

How to set up a configuration file for TypeScript

To create a configuration file for TypeScript, run the command below*:

tsc --init

The tsconfig.json file is the root directory. You can customize it.

*If the commands above are not working, add "npx" (e.g., npx tsc --watch) to the commands and run again.

How to compile TypeScript

You can start TypeScript with the following command:

tsc

If it's successfully compiled, you should see a JavaScript file in your folder.

To follow the TypeScript file changes*:

tsc --watch

If you want to follow the JavaScript file changes, run the command below:

node --watch index.js

Don't forget to change the file name if your file name is not "index.js". Please note that you need to have Node version 18.11 or newer to use the watch command. Otherwise, you need to update the node. You can check the node version with the node -v command.

*If the commands above are not working, add "npx" (e.g., npx tsc --watch) to the commands and run again.

Primitive Types

There are 7 primitive data types: string, number, bigint, boolean, undefined, null, symbol. Primitives are immutable. You can change the value, but you can't change the type later. You can explicitly or implicitly assign a type. Let's see examples:

let age : number = 20
let fav: number;
fav = 5
let firstName = "Pepe"
firstName = 9 //ERROR: Type number is not assignable to type string

Arrays

You can specify the array type in TypeScript:

let list : string[] = ["one", "two", "three", "four"]
let list: Array<string> = ["one", "two", "three", "four"];
let mixed = ["Jenny", 5, true]
mixed[3] = false

The type for the list array above is defined in two different ways. They are identical, and you can choose the syntax you want. In the list array, all the elements should be string type. You can add new elements, but they all have to be string type. In the mixed array, the type is not explicitly specified, but it only contains strings, numbers, and booleans — so any new elements must be of those types.

Multi-dimensional Arrays

let arr: string[][] = [['str1', 'str2'], ['more', 'strings']];

any type

When a variable is declared without an initial value or type, it is considered to be of type any, similar to a JavaScript variable.

  let x;
  x = 245
  x = "Jenny"

Tuples

Tuples are similar to arrays, but they have a fixed size and can contain elements of the same or different types, arranged in a specific order. You cannot change the length of a tuple or the order of its types.

let tupleOne: [string, number, boolean] = ['black', 3 , false];

let tupleTwo: [number, number, number] = [1, 2, 3, 4];
// Type Error! numbersTuple should only have three elements.
let tupleThree: [number, string, boolean] = ['hi', 3, true]
// Type Error! The first elements should be a number, the second a string, and the third a boolean.

Note that arrays and tuples have different syntax:

let tupleOne: [string, string, string]; // tuple

let tupleTwo: string[]; // array

Let's join arrays with a tuple:

let tup: [number, number, number] = [1,2,3];
let concatResult = tup.concat([4,5,6]);
// concatResult has the value [1,2,3,4,5,6].

Functions

TypeScript functions allow you to specify the types of both the input and output values.

function greeting(name: string){
console.log(`Hello ${name}`)}

The input values are defined in the example above. If you get the "Duplicate function implementation." error, you can write export{} at the top of your TypeScript file.

const greeting2 = (name: string): void => {console.log(`Hello ${name}`)}

void signifies the absence of a value. In the example above, both input and output types are explicitly defined.

Optional Parameters

If you want to create an optional parameter, add ? after the parameter:

function greet(name?: string) {
    console.log(`Hello, ${name || 'Anonymous'}!`);}

Default Parameters

You can create a function with a default parameter:

function greet(name = 'Anonymous') {
    console.log(`Hello, ${name}!`);}

function greet(name = 'Anonymous')
{ console.log(`Hello, ${name}!`);}

Type Aliases, Interfaces, Intersection Types

Type Aliases

You can use type to define types with a custom name. Type aliases are similar to interfaces, but they can name primitives, unions, and tuples. It helps to prevent code repetition.

  type Item = {
    color: string,
    size: string,
    amount: number};

const first : Item = {color: "pink", size: "large", amount: 10 }

A type can not be changed after being created. You can only extend it with an intersection.

Interfaces

You can use interfaces to create a structure for objects. They can be shared between objects. Interfaces are similar to types but they can only be used for objects.

interface Member {
name: string;
age: number;
active: boolean; }

const first : Member = {name: "Jane", age: 32, active: false}

You can extend interfaces with word "extends":

interface Product {
color: string;}

interface Square extends Product {
size: number; }

let square1 : Square = {color: "grey", size: 21}

Nesting interfaces inside an interface

You can nest an interface inside a TypeScript interface.

interface Info{
name: string;
age: number
}
interface Membership{
id: number;
info: Info
}

let newPerson: Info = {name: "Jenny", age: 31}
let newMember: Membership = {id: 1463, info: newPerson}
console.log(newMember)

Intersection Types

You can use intersection types to combine existing objects:

interface Member{ name: string;}
interface MemberInfo{ age: number}

type memberDetails = Member & MemberInfo
let member : memberDetails = {name: "John", age: 21}
console.log(member)

The type intersection is used to combine Member and MemberInfo objects in the example above. You can use an intersection type (with the interfaces above) inside a TypeScript function:

function showMember(detail: Member & MemberInfo){
          console.log(`member details: ${detail.name} ${detail.age}`)}
showMember(member)

You can also use the type memberDetails directly:

function showMember(detail: memberDetails){
          console.log(`member details: ${detail.name} ${detail.age}`)}
showMember(member)

Union Types

You can use union types to assign more than one type to a variable:

let num: string | number;
num = 9;
num = '9';

You can use union types for arrays:

let list: (string | number | boolean)[] = ['David', 3, true];

You can use union types for functions:

const add = (a: number, b: number, c?: number | string) => {
console.log(c);
return a + b; }

The parameter c is optional. Please note that you should choose a method that is available on all types within the union. Otherwise, you'll get an error. You can use type guards to avoid type errors.

Literal Types

You can create union types with specific strings and numbers:

let favouriteNumber: 'one' | 'two' | 'three' | 'four';
favouriteNumber = 'blue'; //ERROR: Type '"blue"' is not assignable to type '"one" | "two" | "three" | "four"'.

type Status = "loading" | "loaded" | "not loading"

Type Guards

Type guard checks the type of a variable within conditional blocks.

function changeInput(element: string | number) {
    if (typeof element === 'string') {
      return element.toUpperCase();
// TypeScript knows 'element' is a string here
    } else {
      return element.toString();
// TypeScript knows 'element' is a number here
}}

The Type Guards example above checks the type of the element.

You can use type guards with object literals as well:

type Status = "loading" | "loaded" | "not loading"
function loadingStatus(status: Status){
  if(status == "loading"){
    console.log("loading...")}
  else if(status == "loaded"){
    console.log("loaded")}
  else if(status == "not loading"){
    console.log("not able to load")}}
loadingStatus("loading")

Classes

TypeScript adds types and visibility modifiers to JavaScript classes. You can add properties and methods using classes.

Properties

class Car {
    model: string;}
const car1 = new Car();
car1.model = "Honda";

Visibility

There are three main visibility modifiers in TypeScript:
public, private, protected classes. public class is the default class and its members can be accessed from anywhere. private class members can be accessed from within the class. protected class members can be accessed only by itself and its subclasses.

Enums

Numeric enums

You can define a list of possible values for a variable with enums. Let's see the syntax of enums:

enum Location {
Top,
Bottom,
Right,
Left}
console.log(Location.Top)
// logs 0

If a specific number isn't assigned to the members of the enum, it starts with 0 (like an array) by default. However, you can assign a numeric value:

enum Months{
January = 1,
February,
March,
April }

console.log(Months.February)
// logs 2

If you don't assign a different value, as in the example above, the value of the first member is 0 by default, and you add 1 to each additional value. They can also be assigned to a specific value (e.g., February= 2).

String enums

You assign members of the string enum to specific strings:

enum numberList {
one = 'ONE',
two = 'TWO',
three = 'THREE' }

let x: numberList
x = numberList.one

Generics

Generics are used to create variable types for classes, functions, and type aliases. They help create reusable codes.

Generic Types

Generics for type aliases store the type as a value. Let's see an example of a generic type:

type Movie<G> = {
name: G,
quotes: G[]};

let movie1: Movie<string> = {
name: 'Inside Out',
quotes: ['They Came To Help… Because Of Sadness.', 'Get Lost In There!']};

Another example of the generic type:

type Info<S, R> = {name: S, age: R}
let info1: Info<string, number> = {name:"bella", age: 34}
console.log(info1.name)

The generic type above (type Info) has two types: S and R. info1 variable passes a string type for S and a number type for R. Otherwise, you will get an error.

Generic Functions

function Greeting<C>(name: C): C{
      return name}
console.log(Greeting("Jack"))

C is the type used for the generic function. You should have the same type for both parameters and the result. The value (for the name) has a string type in the example above. However, it could be any type. If you want to get a result with a specific type, you should specify it:

function Greeting2<C>(name: C): string{
    return `Hello ${name}`}
console.log(Greeting2("John"))

If the type string for the result wasn't specified in the example above, the function Greeting2 would fail because the function returns a string. You can still pass any type for the parameter. For example, it can be a number. However, the type (C - in the example above) for the parameter and the result should be the same as the previous example.

Generic Classes

Generic classes are similar to generics with type aliases and JavaScript classes. Let's see a generic class example:

class Team<T, R> {
  constructor(public name: T, public people: R) {}}
let team1 = new Team("Eagles", 20);
console.log(team1)

Generic Constraints

Generics help us to create reusable codes and generic constraints allow us to limit the capabilities of types.

Generic constraint example 1

interface Cats {
name: string;
age: number}

function showCatDetails<C extends Cats> (cat1: C){
    console.log(cat1.name, cat1.age)}

let cat1 : Cats = { name: "Nana", age: 1 };
showCatDetails(cat1)

The result is "Nana 1". The function showCatDetails uses the Cats interface in the example above. The name parameter should be a string and the age parameter should be a number. If they are not, there will be a type error.

Generic constraint example 2

interface Cats2 {
name: string;
age: number}

You can use type parameters in generic constraints:

function showCatDetails2<C extends Cats2, K extends keyof C> (cat: C, name: K): C[K]{
    return cat[name];}

let cat2 : Cats2 = {name: "Speedy", age: 2}
let catName : string = showCatDetails2(cat2, "name")
console.log(`Its name is ${catName}`)

Generic constraint example 3

interface Dog{
name: string;
age: number}

class Details implements Dog{
  constructor(public name: string, public age: number){}
    getInfo(): string {
    return this.name; }}

To call the getInfo function, you need to extend the Details class and declare the function:

function getInfo<T extends Details> (dog: T): void {
    console.log(`${dog.getInfo()} is ${dog.age}`)}
let dog1 = new Details("Brownie", 10)
getInfo(dog1)