DEV Community

Cover image for Get Started With TypeScript in 2019
Robert Cooper
Robert Cooper

Posted on • Originally published at robertcooper.me

Get Started With TypeScript in 2019

Get Started With TypeScript in 2019

Originally posted on my blog

Based on the Stack Overflow Developer survey in 2018, TypeScript is more “loved” as a programming language than JavaScript. The reason TypeScript is so loved amongst JavaScript developers is because adding types to JavaScript allows you to spot errors before running your code. The errors provided by the TypeScript compiler will give a good indication of how an error can be fixed. Adding types to JavaScript also allows code editors to provide some more advanced features, such as code completion, project-wide refactoring, and automatic module importing.

Results of the most loved programming languages survey question from the 2018 Stack Overflow SurveyResults of the “most loved programming languages” survey question from the 2018 Stack Overflow Developer Survey

Learning TypeScript might seem intimidating if you come to think of it as a completely new programming language. However, TypeScript is just an added layer to JavaScript and you by no means have to know every bit of syntax that comes along with TypeScript before you can start using it. TypeScript allows you to easily convert a JavaScript file by changing the file extension from .js to .ts and all the code will compile properly as TypeScript. You can configure TypeScript to be more restrictive if you want to enforce a larger percentage of type coverage in your TypeScript files, but that can be done once you get more familiar with the language.

This article aims to bring you up to speed with around 95% of the scenarios you will typically encounter in a standard TypeScript project. For that last 5%, well, Google is your friend and I’ve added links to useful TypeScript resources at the bottom of the article.


Setting Up TypeScript

Of course, to begin writing TypeScript that compiles correctly, a properly configured development environment is required.

1. Install the TypeScript compiler

To start off, the TypeScript compiler will need to be installed in order to convert TypeScript files into JavaScript files. To do this, TypeScript can either be installed globally (available anywhere in your file system) or locally (only available at the project level).

# NPM Installation Method

npm install --global typescript # Global installation
npm install --save-dev typescript # Local installation

# Yarn Installation Method

yarn global add typescript # Global installation
yarn add --dev typescript # Local installation
Enter fullscreen mode Exit fullscreen mode

Commands to run from the command line to install TypeScript globally or locally on your computer

2. Make sure your editor is setup to support TypeScript

You’ll want to make sure your editor is properly configured to work with TypeScript. For example, you may need to install a plugin (such as atom-typescript if using the atom editor), in order to fully take advantage of TypeScript in your editor. If using VS Code, TypeScript support is built-in, so there are no extensions required 😎.

3. Create a **tsconfig.json** file

A tsconfig.json file is used to configure TypeScript project settings. The tsconfig.json file should be put in the project's root directory. The file allows you to configure the TypeScript compiler with different options.

You can have the tsconfig.json contain an empty JSON object if you just want to get TypeScript to work, but if you need the TypeScript compiler to behave differently (such as output transpiled JavaScript files in a specific output directory), you can read more about which settings can be configured.

Note: You can also run the _tsc --init_ to generate a _tsconfig.json_ file with some default options set for you and a bunch of other options commented out

4. Transpile TypeScript to JavaScript

In order to transpile your TypeScript code to JavaScript, the tsc command needs to be run in the terminal. Running tsc will have the TypeScript compiler search for the tsconfig.json file which will determine the project's root directory as well as which options to use when compiling the TypeScript and transpiling .ts files to .js files.

To quickly test that the setup works, you can create a test TypeScript file and then run tsc in the command line and see if a JavaScript file is generated beside the TypeScript file.

For example, this TypeScript file…

const greeting = (person: string) => {
    console.log('Good day ' + person);
};

greeting('Daniel');
Enter fullscreen mode Exit fullscreen mode

Example TypeScript syntax

Should transpile to this JavaScript file…

var greeting = function(person) {
    console.log('Good day ' + person);
};

greeting('Daniel');
Enter fullscreen mode Exit fullscreen mode

JavaScript file that gets generated from transpiling TypeScript

If you’d like for the TypeScript compiler to watch for changes in your TypeScript files and automatically trigger the transpilation of .ts to .js files, you can run the tsc -p. command in your project’s repository.

In VS Code, you can use ⌘⇧B to bring up a menu that can run the transpiler in either normal or watch modes (tsc:build or tsc:watch, respectively).

The VS Code build tasks menu that can be brought up using ⌘⇧BThe VS Code build tasks menu that can be brought up using ⌘⇧B


Understanding Static and Dynamic Types

JavaScript comes with 7 dynamic types:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol
  • Object

The above types are called dynamic since they are used at runtime.

TypeScript brings along static types to the JavaScript language, and those types are evaluated at compile time (without having to run the code). Static types are what predict the value of dynamic types and this can help warn you of possible errors without having to run the code.


Basic Static Types

Alright, let’s dive into the syntax of TypeScript. What follows are the most commonly seen types in TypeScript.

Note: I’ve left out the _never_ and _object_ types since, in my experience, they are not very commonly used.

boolean

The simple true and false values you've come to know and love.

let isAwesome: boolean = true;
Enter fullscreen mode Exit fullscreen mode

boolean type annotation

string

Textual data surrounded in either single quotes ('), double quotes ("), or back ticks.

let name: string = 'Chris';
let breed: string = 'Border Collie';
Enter fullscreen mode Exit fullscreen mode

string type annotation

If using back ticks, the string is called a template literal and expressions can be interpolated within them.

let punchline: string = 'Because it was free-range.';
let joke: string = `
    Q: Why did the chiken cross the road?
    A: ${punchline}
`;
Enter fullscreen mode Exit fullscreen mode

string type annotation with the use of template literals

number

Any floating point number is given the type of number. The four types of number literals that are supported as part of TypeScript are decimal, binary, octal and hexadecimal.

let decimalNumber: number = 42;
let binaryNumber: number = 0b101010; // => 42
let octalNumber: number = 0o52; // => 42
let hexadecimalNumber: number = 0x2a; // => 42
Enter fullscreen mode Exit fullscreen mode

number type annotation

Note: If the binary, octal, and hexadecimal numbers confuse you, you’re not alone.

array

Array types in TypeScript can be written in two ways. The first way requires that [] be postfixed to the type of elements that are found in the array.

let myPetFamily: string[] = ['rocket', 'fluffly', 'harry'];
Enter fullscreen mode Exit fullscreen mode

array of strings using the square bracket notation

The alternative way to write Array types is to use Array followed by the type of elements that are found in the array (within angle brackets).

let myPetFamily: Array<string> = ['rocket', 'fluffly', 'harry'];
Enter fullscreen mode Exit fullscreen mode

array of strings using the angle bracket notation

tuple

A tuple is an array that contains a fixed number of elements with associated types.

let myFavoriteTuple: [string, number, boolean];

myFavoriteTuple = ['chair', 20, true]; // ✅
myFavoriteTuple = [5, 20, true]; // ❌ - The first element should be a string, not a number
Enter fullscreen mode Exit fullscreen mode

Declaring a tuple with 3 elements and then assigning values to the tuple

enum

An enum is a way to associate names to a constant value, which can be either a number or a string. Enums are useful when you want to have a set of distinct values that have a descriptive name associated with it.

By default, enums are assigned numbers that start at 0 and increase by 1 for each member of the enum.

enum Sizes {
    Small,
    Medium,
    Large,
}

Sizes.Small; // => 0
Sizes.Medium; // => 1
Sizes.Large; // => 2
Enter fullscreen mode Exit fullscreen mode

Example of an enum starting at 9

The first value can be set to a value other than 0.

enum Sizes {
    Small = 1,
    Medium,
    Large,
}

Sizes.Small; // => 1
Sizes.Medium; // => 2
Sizes.Large; // => 3
Enter fullscreen mode Exit fullscreen mode

Example of the enum starting at a value other than 0

Enums are by default assigned numbers, however, string values can also be assigned to an enum.

enum ThemeColors {
    Primary = 'primary',
    Secondary = 'secondary',
    Dark = 'dark',
    DarkSecondary = 'darkSecondary',
}
Enter fullscreen mode Exit fullscreen mode

Example of an enum with string values

any

If the type of a variable is not known and we don’t want the type checker to complain at compilation time, then the type of any can be used.

let whoKnows: any = 4; // assigned a number

whoKnows = 'a beautiful string'; // can be reassigned to a string
whoKnows = false; // can be reassigned to a boolean
Enter fullscreen mode Exit fullscreen mode

Example of the any type

any will likely frequently be used when starting out with TypeScript. However, it's best to try to reduce the usage of any since the usefulness of TypeScript decreases when the compiler isn't aware of the types associated with variables.

void

When there is no type associated with something, the void type should be used. It is most commonly used when specifying the return value of a function that doesn't return anything.

const darkestPlaceOnEarth = (): void => {
    console.log('Marianas Trench');
};
Enter fullscreen mode Exit fullscreen mode

Example of using the void type

null and undefined

Both null and undefined correspond to the types of the null and undefined values you might see in JavaScript. These types aren't very useful when used on their own.

let anUndefinedVariable: undefined = undefined;
let aNullVariable: null = null;
Enter fullscreen mode Exit fullscreen mode

Example of how the null and undefined types could be used

By default the null and undefined types are subtypes of all other types, meaning that a variable of type string can be assigned a value of null or undefined. This is often undesirable behavior and thus it’s usually recommended to set the strictNullChecks compiler option in a tsconfig.json file to true. Setting the strictNullChecks option to true makes it so that null and undefined need to be explicitly set as a type for a variable.


Type Inference

Fortunately, you don’ have to specify types absolutely everywhere in your code because TypeScript has what is called Type Inference. Type inference is what the TypeScript compiler uses to automatically determine types.

Basic Type Inference

TypeScript can infer types during variable initialization, when default parameter values are set, and while determining function return values.

// Variable initialization
let x = 10; // x is given the number type
Enter fullscreen mode Exit fullscreen mode

Example of type inference where the x variable has an inferred type of number

In the above example, x is assigned a number, TypeScript associates the x variable with a type of number.

// Default function parameters
const tweetLength = (message = 'A default tweet') => {
    return message.length;
};
Enter fullscreen mode Exit fullscreen mode

An inferred type of string is given to the message parameter

In the above example, the message parameter is assigned a default value which is of type string, so therefore the TypeScript compiler infers that message is of type string and therefore doesn’t throw a compilation error when the length property is being accessed.

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

const result = add(2, 4);

result.toFixed(2); // ✅
result.length; // ❌ - length is not a property of number types
Enter fullscreen mode Exit fullscreen mode

An inferred type of number is assigned to the return value of the add function based on the types of the function’s parameters

In the above example, since TypeScript is told that both parameters to the add function have a type of number, it can infer that the return type will also be a number.

Best Common Type Inference

When a type is being inferred from multiple possible types, TypeScript uses a “best common type” algorithm to pick a type that works with all the other candidates.

let list = [10, 22, 4, null, 5];

list.push(6); // ✅
list.push(null); // ✅
list.push('nope'); // ❌ - type 'string' is neither of type 'number' or 'null'
Enter fullscreen mode Exit fullscreen mode

The best common type algorithm determines that only number and null types should be allowed as elements to the list array

In the above example, the array is composed of both number and null types, and therefore TypeScript expects only number and null values to be a part of the array.


Type Annotation

When the Type Inference system is not enough, you will need to declare types on variables and objects.

Basic Types

All the types introduced in the Basic Static Types section can be declared using a : followed by the name of the type.

let aBoolean: boolean = true;
let aNumber: number = 10;
let aString: string = 'woohoo';
Enter fullscreen mode Exit fullscreen mode

Examples of annotating basic types

Arrays

As shown in the section talking about the array type, arrays can be annotated one of two ways.

// First method is using the square bracket notation
let messageArray: string[] = ['hello', 'my name is fred', 'bye'];

// Second method uses the Array keyword notation
let messageArray: Array<string> = ['hello', 'my name is fred', 'bye'];
Enter fullscreen mode Exit fullscreen mode

Annotating arrays

Interfaces

One way to put together multiple type annotations is by using an interface.

interface Animal {
    kind: string;
    weight: number;
}

let dog: Animal;

dog = {
    kind: 'mammal',
    weight: 10,
}; // ✅

dog = {
    kind: true,
    weight: 10,
}; // ❌ - kind should be a string
Enter fullscreen mode Exit fullscreen mode

Annotating types using an interface

Type Alias

To make things confusing, TypeScript also allows you to specify multiple type annotations using a type alias.

type Animal = {
    kind: string;
    weight: number;
};

let dog: Animal;

dog = {
    kind: 'mammal',
    weight: 10,
}; // ✅

dog = {
    kind: true,
    weight: 10,
}; // ❌ - kind should be a string
Enter fullscreen mode Exit fullscreen mode

Annotating types using a type alias

What seems to be the best practice in terms of using an interface or a type alias is that you should generally just pick either interface or type in your codebase and be consistent. However, if writing a 3rd party public API that can be used by others, use an interface type.

If you want to get a more detailed comparison between the type alias and an interface, I would recommend this article by Matin Hochel.

Inline Annotations

Instead of creating a re-usable interface, it might be more appropriate to annotate a type inline instead.

let dog: {
    kind: string;
    weight: number;
};

dog = {
    kind: 'mammal',
    weight: 10,
}; // ✅

dog = {
    kind: true,
    weight: 10,
}; // ❌ - kind should be a string
Enter fullscreen mode Exit fullscreen mode

Using an inline type annotation

Generics

There are situations where the specific type of a variable doesn’t matter, but a relationship between the types of different variables should be enforced. For those cases, generic types should be used.

const fillArray = <T>(len: number, elem: T) => {
    return new Array<T>(len).fill(elem);
};

const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']

newArray.push('bye'); // ✅
newArray.push(true); // ❌ - only strings can be added to the array
Enter fullscreen mode Exit fullscreen mode

Using generic types to define type relationships

The above example has a generic type T that corresponds to the type of the second argument passed to the fillArray function. The second argument passed to the fillArray function is a string, and therefore the created array will have all of its elements set to have a type of string.

It should be noted that it is by convention that single letters are used for generic types (e.g. T or K). However, there is nothing stopping your from using more descriptive names for your generic types. Here is the above example with a more descriptive name for the supplied generic type:

const fillArray = <ArrayElementType>(len: number, elem: ArrayElementType) => {
    return new Array<ArrayElementType>(len).fill(elem);
};

const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']

newArray.push('bye'); // ✅
newArray.push(true); // ❌ - only strings can be added to the array
Enter fullscreen mode Exit fullscreen mode

Using more descriptive names for generic types

Union Type

In scenarios where a type can be one of multiple types, a union type is used by separating the different type options with a |.

// The `name` parameter can be either a string or null
const sayHappyBirthdayOnFacebook = (name: string | null) => {
    if (name === null) {
        console.log('Happy birthday!');
    } else {
        console.log(`Happy birthday ${name}!`);
    }
};

sayHappyBirthdayOnFacebook(null); // => "Happy birthday!"
sayHappyBirthdayOnFacebook('Jeremy'); // => "Happy birthday Jeremy!"
Enter fullscreen mode Exit fullscreen mode

An example of a union type annotation

Intersection Type

An intersection type uses the & symbol to combine multiple types together. This is different than the union type, as a union type says "the resulting type is one of the listed types" whereas the intersection type says "the resulting type is the combination of all listed types".

type Student = {
    id: string;
    age: number;
};

type Employee = {
    companyId: string;
};

let person: Student & Employee;

person.age = 21; // ✅
person.companyId = 'SP302334'; // ✅
person.id = '10033402'; // ✅
person.name = 'Henry'; // ❌ - name does not exist in Student & Employee
Enter fullscreen mode Exit fullscreen mode

An example of an intersection type annotation

Tuple Type

Tuples are annotated using a : followed by a comma separated list of types within square brackets.

let list: [string, string, number];

list = ['apple', 'banana', 8.75]; // ✅
list = ['apple', true, 8.75]; // ❌ - the second argument should be of type string
list = ['apple', 'banana', 10.33, 3]; // ❌ - the tuple specifies a length of 3, not 4
Enter fullscreen mode Exit fullscreen mode

Annotating a variable using a tuple type

Optional Types

There may be instances where a function parameter or object property is optional. In those cases, a ? is used to denote these optional values.

// Optional function parameter
function callMom(message?: string) {
    if (!message) {
        console.log('Hi mom. Love you. Bye.');
    } else {
        console.log(message);
    }
}

// Interface describing an object containing an optional property
interface Person {
    name: string;
    age: number;
    favoriteColor?: string; // This property is optional
}
Enter fullscreen mode Exit fullscreen mode

Defining optional types


Useful Resources

For the parts of TypeScript that weren’t covered in this article, I recommend the following resources.

TypeScript Handbook (Official TypeScript docs)

TypeScript Deep Dive (Online TypeScript Guide)

Understanding TypeScript’s Type Annotation (Great introductory TypeScript article)

Top comments (18)

Collapse
 
tomekbuszewski profile image
Tomek Buszewski

Hey Robert, great article!

I am using TypeScript myself and, to be frank, at first I thought bad about it. But then I went to a project without any typing and I saw the benefits from static typings :)

One question though. When I work with large objects, for example a response from REST, and doing a model, should I write the entire interface for it? It can have many optional fields, given the quality of the API itself. How should I approach it?

Collapse
 
robertcoopercode profile image
Robert Cooper

That's a great question, and i'm not sure what the best approach is here. My intuition tells me that
the API response doesn't require an interface, but rather the model/object you create from the API response should be typed. The reason I say this is that you don't have control over fields the API returns, but you do have control over the properties on the model/object that you create from the API response.

Hopefully that wasn't too abstract of an answer and that it made sense.

Collapse
 
tomekbuszewski profile image
Tomek Buszewski

Hey Robert, and thanks for your answer.

While I agree with you, when I create a model, I need to know on which fields I can operate. This is a very common problem for me recently as I work with a lot of CMS' and integrate them into our app via modeling.

Collapse
 
blindfish3 profile image
Ben Calder

You do of course need to type what you expect to get in the response and then use within your application. In my experience any unused data needn't be typed...

But this is one situation where IMO typings can give a false sense of security: API responses that are out of your control can (and often will) change unexpectedly - e.g. return null on a property where you don't expect it; so it's still important to have some guards in place to avoid responses that might crash your application.

Collapse
 
karataev profile image
Eugene Karataev

Thanks for the great article!
I'm new to TypeScript and couple of days ago I decided to convert my Node JS project to TS for better maintability. I heard many times that firts steps should be painless - just rename .js to .ts.

TypeScript allows you to easily convert a JavaScript file by changing the file extension from .js to .ts and all the code will compile properly as TypeScript.

Nevertheless, I faced couple of problems from the beginning.
Consider the most basic Node project:

// hello.ts
function hello() {
  console.log('hello');
}

module.exports = {
  hello
};

// index.ts
const {hello} = require('./hello');

hello();

JS version works as expected, but TS throws

error TS2451: Cannot redeclare block-scoped variable 'hello'.

I had to replace require to import across all project files to fix the issue.

Another problem popped up when I tried to compile TS files without types for 3rd party libraries.

Anyway, right now I'm in the middle of moving the project to TS and I feel much more confident with it's logic and data structures compared to the raw JS version.

Collapse
 
mouvedia profile image
G. • Edited

Is there a type that corresponds to () => any? Is it interchangeable with Function?

Collapse
 
cookavich profile image
Paul Cook

You could type a function like that:

function foo(): () => any {
  return 'bar';
}

I generally avoid using the any type, so, in that case I would type it like so:

function foo(): () => string {
  return 'bar';
}

Same goes for arguments.

I see many gifted devs default to any a lot when they're first getting into TypeScript. I think that is fine as a start, but as a goal I would want to have the proper types for arguments/return values.

I think a heavy use of any generally indicates a lack of clarity about your program, and if you have a lack of clarity than anyone who works on the code after you will be even more unclear.

Why don't you know what the types are? And how can you better figure them out?

Collapse
 
sagarb3 profile image
Sagar Bhattacharya

Good Point , whenever we are writing code we should be very sure of the input and output , which leads to clarity of logic as well as a good code-base , simple to read and simple to extend. When I started coding I was very poor about making this decision , but overtime I have learned that even if you cannot adhere to strict types in early phase , you must make sure to correct those things in self-review and make it a part of the code writing to be very clear about all the possible input and outputs.

Collapse
 
robertcoopercode profile image
Robert Cooper • Edited

Good question. There is no function type. When annotating types for a function, you must specify the types of the parameters and the return values.

Collapse
 
ant profile image
Ant

really great,I can't wait to use Typescript after reading your article.

Collapse
 
stealthmusic profile image
Jan Wedel

Hi, great summary! Thanks for writing, I will keep it in my bookmarks.

Collapse
 
vladyn profile image
Vladimir Varbanov

great introduction - many thanks!

Collapse
 
reisclef profile image
Richard C

Excellent summary. I just started with typescript a couple weeks ago and am enjoying working with it. I may refer back to this as "quick reference". ;)

Collapse
 
cagataysert profile image
Çağatay Sert

Thanks a lot. It was very helpful article for me :))

Collapse
 
mochfamir profile image
Mochamad Faishal Amir

Hi Robert this article very helpful, I learn Angular and also must have knowledge about Typescript.

Collapse
 
varmab profile image
Varma Bhupatiraju

Nice intro to TypeScript. Thank you.

Collapse
 
uf4no profile image
Antonio

Very good article, thanks for sharing.
Do you have any tips regarding debugging in TS?

Collapse
 
robertcoopercode profile image
Robert Cooper

Debugging TS errors can sometimes be tricky since they are not always straightforward. However, the more you work with TS, the more better you'll get interpreting the errors.