Table of Contents

Installing TypeScript

TypeScript in Your Project

				
					npm install typescript --save-dev

				
			

Globally Installing TypeScript

				
					npm install typescript -g
				
			

Checking TypeScript Version

				
					tsc -v

<!--Version 5.4.2-->
				
			

TypeScript Configuration File

add the TypeScript configuration file to the root of your project: tsconfig.json

  • There are plenty of configurations available. But we start with just one: “include”
  • Then we supply it a parameter of the paths to the files that we’d like to include.
				
					{
  "include": ["src/**/*"] // Look for all of the files under the src or source folder under this current folder. This defines the scope of this TypeScript project.
}
				
			

Run the TypeScript compiler to see what happens:

				
					tsc
error TS18003: No inputs were found in config file '/Users/xxx/TypeScript/project_01/tsconfig.json'. Specified 'include' paths were '["src/**/*"]' and 'exclude' paths were '[]'.


Found 1 error.
				
			

The above error message means that there is no file to compile in the defined path.

By adding a new TypeScript file in the include path and adding some JavaScript code to it you can test it again to see that the error has gone now.

You can define which JavaScript version TypeScript file should be compiled to by adding it to “compilerOptions” object in “target” configuration.

By default, TypeScript generate the js file at the path of the ts file. You can also change the path to a new path by defining it in the “outDir” ¬†property.

				
					{
  "compilerOptions": {
    "outDir": "build", // Defining the directory of compiled ts files
    "target": "ES6" // Defining the version of the compiled js files by TypeScript
  },
  "include": ["src/**/*"]
}
				
			

If you have plan to use TypeScript only for its type checking ability, and will rely on another tool, such as the Babel compiler to transpile the final output, then an even better value for the target setting is “esnext”.

				
					{
  "compilerOptions": {
    "outDir": "build",
    "target": "ESNext", // This setting tells TypeScript to emit code that is compatible with whatever the latest version of JavaScript happens to be
  },
  "include": ["src/**/*"]
}
				
			

Which effectively just means that it should strip out all the type annotations and pass the rest of the code onto the other tools.

Also if you do plan on using TypeScript only for type checking and nothing more, it’s a good idea to set the noEmit setting to true. Which tell TypeScript not to write out anything to disk at all.¬†

				
					{
  "compilerOptions": {
    "outDir": "build",
    "target": "ESNext",
    "noEmit": true
  },
  "include": ["src/**/*"]
}
				
			

Adding type checking to JavaScript file

For this purpose, add the following parameters to “compilerOptions” object and set them to true.(Lines 3 and 4):

				
					{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,
    "outDir": "build",
    "target": "ESNext",
    "noEmit": true
  },
  "include": ["src/**/*"]
}
				
			

Primitives and built-in types

To define a variable type use colon as follow:

				
					let x: number
let y: string
let z: boolean
let a: Date
let b: string[]

b = "Hello!" // Since we defined b as an array, assigning a string returns an error in TypeScript. To solve the problem you can use the following line trick, but not recommenden!
b = "Hello!" as any
				
			

Creating custom types with interfaces

If you are familiar with the more recent versions of JavaScript and the class syntax, this might look incredibly familiar.

In fact, any class definitions created using the JavaScript syntax can also be used as interfaces.

The biggest difference between an interface and a class however, is that interfaces strictly exist as a way to provide type information to TypeScript. They are only ever used at compile time and are never available, and will never even appear in your runtime code.

				
					interface Contact {
    id: number;
    name: string;
    birthDate: Date;
}

let primaryContact: Contact = {
    birthDate: new Date('21-03-1982'),
    id: 12345,
    name: "Hoomaan Sheikholeslami"
}
				
			

To make any of these variables optional, use question mark after the name of the variable and before the colon:

				
					interface Contact {
    id: number;
    name: string;
    birthDate?: Date;
}

let primaryContact: Contact = {
    id: 12345,
    name: "Hoomaan Sheikholeslami"
}
				
			

Merging interfaces

				
					interface Contact extends Address {
    id: number;
    name: string;
    birthDate: Date;
}

interface Address {
    line1: string;
    line2: string;
    province: string;
    region: string;
    postalCode: string;
}
				
			

Defining types using type aliases

				
					interface Contact {
  id: number;
  name: ContactName;
  birthDate: Date;
}

type ContactName = string;

let primaryContact: Contact = {
  birthDate: new Date("21-03-1982"),
  id: 12345,
  name: "Hoomaan Sheikholeslami",
};
				
			

Defining enumerable types

				
					interface Contact {
  id: number;
  name: ContactName;
  birthDate: Date;
  status: ContactStatus;
}

type ContactName = string;

enum ContactStatus {
  Active = "active",
  Inactive = "inactive",
  New = "new",
}

let primaryContact: Contact = {
  birthDate: new Date("21-03-1982"),
  id: 12345,
  name: "Hoomaan Sheikholeslami",
  status: ContactStatus.Active,
};

				
			
When hover on Active, it shows the value of the parameter in enum

Typing functions

				
					interface Contact {
    id: number;
    name: string;
}

function clone(source: Contact): Contact {
    return Object.apply({}, source);
}

const a: Contact = { id: 123, name: "Homer Simpson" };
const b = clone(a)
				
			

Defining a metatype using generics

A generic type is simply a metatype, a type that represents any other type that you might want to substitute in.

				
					interface Contact {
    id: number;
    name: string;
}

function clone<T>(source: T): T {
    return Object.apply({}, source);
}

const a: Contact = { id: 123, name: "Homer Simpson" };
const b = clone(a)
				
			
When hover on clone(a) it shows that the type of it is Contact

You can define as many generic variables as you wish:

				
					interface Contact {
  id: number;
  name: string;
}

interface UserContact {
  id: number;
  name: string;
}

function clone<T1, T2>(source: T1): T2 {
  return Object.apply({}, source);
}

const a: Contact = { id: 123, name: "Homer Simpson" };
const b = clone<Contact, UserContact>(a);

				
			

Generic constraints: which allow you to place more restrictive rules around the types that may be used as generic type parameters in your functions.

				
					interface Contact {
  id: number;
  name: string;
}

interface UserContact {
  id: number;
  name: string;
  username: string;
}

function clone<T1, T2 extends T1>(source: T1): T2 {
  return Object.apply({}, source);
}

const a: Contact = { id: 123, name: "Homer Simpson" };
const b = clone<Contact, UserContact>(a);

				
			

Generic types can be applied to interfaces in classes too.

Here I’ve used the generic syntax to define a generic type named TExternalId on the UserContact interface. Once I’ve defined the generic type, I can then refer to it whenever I want inside of the interface, whether it be the type of a property or even a generic parameter to another interface.

				
					interface Contact {
  id: number;
  name: string;
}

interface UserContact<TExternalId> {
  id: number;
  name: string;
  username: string;
  externalId: TExternalId;
  loadExternalId(): Task<TExternalId>
}

function clone<T1, T2 extends T1>(source: T1): T2 {
  return Object.apply({}, source);
}

const a: Contact = { id: 123, name: "Homer Simpson" };
const b = clone<Contact, UserContact>(a);

				
			

More complex types

Cobining multiple types with union types

				
					// Using pipe syntax
type ContactBirthDate = Date | number | string
				
			

Alternative to extends: &

				
					interface Contact  {
    id: number;
    name: ContactName;
    birthDate?: ContactBirthDate;
    status?: ContactStatus;
}

interface Address {
    line1: string;
    line2: string;
    province: string;
    region: string;
    postalCode: string;
}

type AddressableContact = Contact & Address
				
			

Alternative to enum: type

				
					// enum
enum ContactStatus {
    Active = "active",
    Inactive = "inactive",
    New = "new"
}

// enum usage
let primaryContact: Contact = {
    id: 12345,
    name: "Jamie Johnson",
    status: ContactStatus.Active
}

// Alternative by using type
type ContactStatus = "active" | "inactive" | "new"

// type usage
let primaryContact: Contact = {
    id: 12345,
    name: "Jamie Johnson",
    status: "active"
}