0% found this document useful (0 votes)
2 views

Introduction to TypeScript

TypeScript is an open-source, object-oriented programming language developed by Microsoft that enhances JavaScript with static type checking, class-based objects, and modularity, making it suitable for large-scale projects. It offers features like optional static typing, support for ES6 syntax, and various built-in and user-defined data types, along with a compiler to convert TypeScript code into JavaScript. The language is gaining popularity due to its ability to improve code quality, prevent bugs, and provide a better developer experience, especially in complex applications.

Uploaded by

Nathiya Arumugam
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
2 views

Introduction to TypeScript

TypeScript is an open-source, object-oriented programming language developed by Microsoft that enhances JavaScript with static type checking, class-based objects, and modularity, making it suitable for large-scale projects. It offers features like optional static typing, support for ES6 syntax, and various built-in and user-defined data types, along with a compiler to convert TypeScript code into JavaScript. The language is gaining popularity due to its ability to improve code quality, prevent bugs, and provide a better developer experience, especially in complex applications.

Uploaded by

Nathiya Arumugam
Copyright
© © All Rights Reserved
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 45

Introduction to TypeScript

TypeScript is an open-source, object-oriented programming


language developed and maintained by Microsoft Corporation. Its
journey began in 2012, and since then, it has gained significant
traction in the developer community. It is a Strict Super Set of
JavaScript, which means anything implemented in JavaScript can
be implemented using TypeScript along with adding enhanced
features (every existing JavaScript Code is a valid TypeScript
Code). As TypeScript code is converted to JavaScript code it
makes it easier to integrate into JavaScript projects. It is designed
mainly for large-scale projects.
Key Features of TypeScript
1. Static Type Checking (Optional)
TypeScript allows you to check and assign types to variables,
parameters, and function return values. While this step requires a
little more effort, it significantly improves code quality. Optional
static typing helps prevent bugs and makes your code more
readable.
2. Class-Based Objects
One of TypeScript’s standout features is its support for classes.
Unlike JavaScript’s prototype-based approach, TypeScript lets you
write true object-oriented code. You can create classes, define
constructors, and use inheritance and access modifiers (public,
private, protected).
3. Modularity
TypeScript promotes modularity. By using modules, you can
organize your code into smaller, reusable pieces. This modularity
enhances maintainability and collaboration among team
members.
4. ES6 Features
TypeScript embraces ECMAScript 6 (ES6) features. If you’re
familiar with ES6 syntax (arrow functions, template literals,
destructuring, etc.), you’ll feel right at home with TypeScript.
5. Syntactical Sugaring
TypeScript’s syntax is closer to that of high-level languages
like Java. It’s like a sweetener for developers—more concise and
expressive.
Structure of TypeScript
TypeScript Code cannot be interpreted by the Browser directly so
there is a need to compile the TypeScript code into plain
JavaScript Code, for this purpose we need the TypeScript
Compiler (tsc).

TypeScript Compiler (tsc)


 Written in TypeScript itself.
 Compiles .ts files to .js files.
 Installed as an NPM package (NodeJS).
 Supports ES6 syntax.
TypeScript Vs. JavaScript
TypeScript JavaScript

It is an Object Oriented Language (Class It is an Object Based Language


based) (Prototype based)

Statically Typed language Dynamically Typed language

Supports Modules Does not Support Modules

Provides Errors at Compile time / during


Doesn’t provide Compile time errors
development

Takes more time as the code needs to be


No need of compilation
Compiled

Why TypeScript is Gaining Popularity ?


 Initially, JavaScript was designed for lightweight, simple DOM
manipulations. However, as web applications grew in
complexity, developers needed more robust tools. TypeScript
stepped in to address these needs.
 Classes and Objects: TypeScript’s support for classes and
objects simplifies implementing object-oriented concepts. It’s
easier to reason about and maintain code when you have
proper class-based structures.
 Frameworks and Libraries: TypeScript’s adoption by popular
frameworks like Angular has contributed to its rising
popularity. Developers appreciate the enhanced features and
the ability to write cleaner, safer code.
Why Do We Use TypeScript ?
1. Better developer experience – One of the biggest
advantages of TypeScript is to enable IDEs to provide a richer
environment for spotting common errors as you type the code.
For a large scale project adopting TypeScript might result in
more robust software, while still being deployable where a
regular JavaScript application would run.
2. Code quality – Defining data structures in the beginning,
using types and interfaces, forces you to think about your app’s
data structure from the start and make better design decisions.
3. Prevents bugs – TypeScript won’t make your software bug
free. But it can prevent a lot of type-related errors. Along with
the Clever IntelliSense many browsers and IDEs support direct
debugging through Source Maps.
4. Active community – TypeScript is getting more and more
popular. It’s used by the top tech companies like Google,
Airbnb, Shopify, Asana, Adobe, and Mozilla so we can assume
that it reaches their expectations in terms of scalability – as
they are developing large and complex applications.
5. TypeScript Is Just JavaScript – TypeScript starts with
JavaScript and ends with JavaScript. Typescript adopts the
basic building blocks of your program from JavaScript. All
TypeScript code is converted into its JavaScript equivalent for
the purpose of execution.

Data types in TypeScript


Last Updated : 01 Jul, 2024




When you create a variable, you’re planning to give it a value. But what
kind of value it can hold depends on the variable’s data type. In
TypeScript, the type system defines the various data types supported by
the language. The data type classification is as given below:
Built-in Datatypes:
TypeScript has some pre-defined data types:
Built-in Data
Type keyword Description

It is used to represent both Integer as well as


Number number
Floating-Point numbers

Boolean boolean Represents true and false

String string It is used to represent a sequence of characters

Void void Generally used on function return-types

It is used when an object does not have any


Null null
value

Undefined undefined Denotes value given to uninitialized variable

If a variable is declared with any data type then


Any any any type of value can be assigned to that
variable

Examples:
let a: null = null;
let b: number = 123;
let c: number = 123.456;
let d: string = ‘Geeks’;
let e: undefined = undefined;
let f: boolean = true;
let g: number = 0b111001; // Binary
let h: number = 0o436; // Octal
let i: number = 0xadf0d; // Hexa-Decimal

User-defined Data Types:


Apart from built-in data types, user can also define its own data type.
User-defined types include Enumerations (enums), classes, interfaces,
arrays, and tuple.
NOTE: In built-in data types,
any: is a special data-type, also the super data-type of all data types. If a
variable is declared with any data type then we can assign any type value
to that variable.
Examples:
let a: any = null;
let b: any =123;
let c: any = 123.456;
let d: any = ‘Geeks’;
let e: any = undefined;
let f: any = true;

TypeScript Operators
Last Updated : 03 Sep, 2024



TypeScript operators are symbols or keywords that perform


operations on one or more operands.
Below are the different TypeScript Operators:
Table of Content
 TypeScript Arithmetic operators
 TypeScript Logical operators
 TypeScript Relational operators
 TypeScript Bitwise operators
 TypeScript Assignment operators
 TypeScript Ternary/conditional operator
 TypeScript Type Operators
 TypeScript String Operators
TypeScript Arithmetic operators
In TypeScript, arithmetic operators are used to perform
mathematical calculations.
Name Description Syntax

Adds two values or a + b


Addition(+) expressions.

Subtracts the right operand a - b


Subtraction(-) from the left operand.
Name Description Syntax

Multiplies two values or a * b


Multiplication(*) expressions

Divides the left operand a / b


Division(/) by the right operand.

Returns the remainder of


the division of the left a % b
operand by the right
Modulus(%) operand.

Increase the value of the


a++ or ++a
Increment(++) operand by 1.

Decrease the value of the


a-- or --a
Decrement(--) operand by 1.

TypeScript Logical operators


In TypeScript, logical operators are used to perform logical
operations on Boolean values.
Name Description Syntax

Returns true if both result = operand1 &&


Logical AND (&&) operands are true. operand2;

Returns true if at least one result = operand1 ||


Logical OR (||) of the operands is true. operand2;

Returns true if the operand


result = !operand;
Logical NOT (!) is false, and vice versa.

TypeScript Relational operators


In TypeScript, relational operators are used to compare two
values and determine the relationship between them.
Name Description Syntax

Returns true if the values


result = operand1 ==
of the two operands are
operand2;
Equal to (==) equal, after type coercion.
Name Description Syntax

Returns true if the values


result = operand1 !=
of the two operands are not
operand2;
Not equal to (!=) equal, after type coercion.

Returns true if the values


of the two operands are result = operand1 ===
equal, without type operand2;
Strictly equal to (===) coercion (strict equality).

Returns true if the values


of the two operands are not result = operand1 !==
Strictly not equal to (! equal, without type operand2;
==) coercion (strict inequality).

Returns true if the value of


the left operand is greater result = operand1 >
than the value of the right operand2;
Greater than (>) operand.

Returns true if the value of


the left operand is less than result = operand1 <
the value of the right operand2;
Less than (<) operand.

Returns true if the value of


the left operand is greater result = operand1 >=
Greater than or equal to than or equal to the value of operand2;
(>=) the right operand.

Returns true if the value of


the left operand is less than result = operand1 <=
Less than or equal to or equal to the value of the operand2;
(<=) right operand

TypeScript Bitwise operators


In TypeScript, bitwise operators perform operations on the binary
representation of numeric values.
Name Description Syntax

Bitwise AND (&) Performs a bitwise AND result = operand1 &


Name Description Syntax

operation between each


operand2;
pair of corresponding bits.

Performs a bitwise OR
result = operand1 |
operation between each
operand2;
Bitwise OR (|) pair of corresponding bits.

Performs a bitwise XOR


(exclusive OR) operation result = operand1 ^
between each pair of operand2;
Bitwise XOR (^) corresponding bits.

Inverts the bits of the


operand, changing result = ~operand;
Bitwise NOT (~) each 0 to 1 and each 1 to 0.

Shifts the bits of the left


operand to the left by the
result = operand1 <<
number of positions
operand2;
specified by the right
Left Shift (<<) operand.

Shifts the bits of the left


operand to the right by the
number of positions result = operand1 >>
specified by the right operand2;
Sign-propagating Right operand, preserving the
Shift (>>) sign bit.

Shifts the bits of the left


operand to the right by the
number of positions result = operand1 >>>
specified by the right operand2;
Zero-fill Right Shift operand, filling the leftmost
(>>>) bits with zeros.

TypeScript Assignment operators


In TypeScript, assignment operators are used to assign values to
variables and modify their values based on arithmetic or bitwise
operations.
Name Description Syntax

Assigns the value of the


right operand to the left variable = value;
Assignment (=) operand.

Adds the value of the right


operand to the current
value of the left operand variable += value;
and assigns the result to the
Addition Assignment (+=) left operand.

Subtracts the value of the


right operand from the
current value of the left variable -= value;
Subtraction Assignment operand and assigns the
(-=) result to the left operand.

Multiplies the current value


of the left operand by the
value of the right operand variable *= value;
Multiplication and assigns the result to the
Assignment (*=) left operand.

Divides the current value


of the left operand by the
value of the right operand variable /= value;
and assigns the result to the
Division Assignment (/=) left operand.

Calculates the remainder


when dividing the current
value of the left operand by variable %= value;
the value of the right
operand and assigns the
Modulus Assignment (%=) result to the left operand.

TypeScript Ternary/conditional operator


In TypeScript, the ternary operator, also known as the conditional
operator, is a concise way to write conditional statements. It
allows you to express a simple if-else statement in a single line.
Name Description Syntax

Evaluates the condition. If


true, condition ?
returns expression_if_true; expression_if_true :
Ternary/Conditional if false, expression_if_false;
Operator returns expression_if_false.

TypeScript Type Operators


In TypeScript, type operators are constructs that allow you to
perform operations on types. These operators provide powerful
mechanisms for defining and manipulating types in a flexible and
expressive manner.
Name Description Syntax

Obtains the type of a let x = 10;<br>type


variable, function, or XType = typeof x;<br>//
typeof property. XType is 'number'

type Person = { name:


string; age:
Obtains the keys (property number };<br>type
names) of a type. PersonKeys = keyof
Person;<br>`// PersonKeys
keyof is ‘name’

Allows creating new types


type Optional<T> = { [K
based on the properties of in keyof T]?: T[K] };
Mapped Types existing types.

type TypeName<T> = T
Allows expressing a type extends string ?
Conditional Types based on a condition. 'string' : 'non-string';

TypeScript String Operators


In TypeScript, string operators and features are used for
manipulating and working with string values.
Name Description Syntax

let fullName = firstName


String Concatenation (+) Concatenates two strings. + " " + lastName;

Template Literals (`) Allows embedding let message = \I am ${age}


Name Description Syntax

expressions inside strings. years old.`;`

Similar to template
let description = "I live
literals, it allows inserting in " + city + ".";
String Interpolation variables into strings.

Various methods for let substring =


String Methods manipulating strings. phrase.substring(7, 15);

String Length Property Returns the length of a let length =


(length) string. message.length;

Variables.
Syntax
TypeScript variables are generally inferred to be whatever basic type of value they are
initially assigned with. Later in code, only values that are of that same basic type may be
assigned to the variable. The term for whether a type is assignable to another type is
assignability.

let myVar: string;

myVar = 'Hello'; // Ok
myVar = 'World!'; // Also Ok
myVar = 42; // Not Ok: Type 'number' is not assignable to type
'string'.

Examples with Variables


In this snippet of code, TypeScript sees that the spooky variable is initially assigned
a boolean value, so it believes the spooky variable should always be of type boolean.
Assigning a variable of type boolean later on is allowed, as a type is assignable to itself:
let spooky = true;

spooky = false; // Ok

If a variable is assigned a different type of value, TypeScript will notice and emit a type
checking complaint. Those type checking complaints can be surfaced as:

 Errors on the command-line.


 Syntax highlights in the code editor.

In this code snippet, the scary variable is initially assigned the value "skeletons", which is
a string type. Later, assigning the decimal number 10.31 is not allowed because
a number type is not assignable to a variable of type string:

let scary = 'skeletons';

scary = 10.31;
// Error: Type 'number' is not assignable to type 'string'

Object Oriented Programming with


TypeScript
#webdev#beginners#typescript#programming

Hey Dev's

How has been your week? I hope everyone had a great week. In todays
tutorial let us go through Object Oriented Programming (OOP).

Object Oriented programming (OOP) is a programming paradigm that


relies on the concept of classes and objects. It is used to structure a
software program into simple, reusable pieces of code blueprints (usually
called classes), which are used to create individual instances of objects

Object programming is well-suited for programs that are large, complex


and actively updated or maintained.

The Object class represents one of JavaScript's data types. It is used to


store various keyed collections and more complex entities. Objects can be
created using the Object() constructor or the object initializer / literal
syntax.

Below is an example of an object class. In this example we have defined a


Person with some attributes and a method.
// person class
class Person {
name: string = '' // default
age: number = 0 // default

greetings(){
return this.name + ' ' + this.age
}
}

// to create a new instance of a class


const person1: any = new Person()
person1.name = 'Kevin Odongo'
person1.age = 36
console.log(person1.greetings()) // This should render Kevin Odongo 36
Benefits of Object Oriented Programming

Some of the benefits of Object Oriented Programming are as follows:

1. Easier debuging
2. Reuse of code through inheritance
3. Flexibility through polymorphism
4. Effective problem solving
5. Project decoupling (Separate project into groups)

Principles of Object Oriented Programming


Encapsulation.
The implementation and state of each object are privately held inside a
defined boundary, or class. Other objects do not have access to this class
or the authority to make changes but are only able to call a list of public
functions, or methods. This characteristic of data hiding provides greater
program security and avoids unintended data corruption.

Take a look at this example. In the example we have defined two object
classes and created an instance of each. So in layman term encapsulation
principle states that the new instance of motor1 cannot access the
attributes of person1. If you try you should get such a warning.

Property 'age' does not exist on type 'Motor'

// person object
class Person {
name: string = ''
age: number = 0
}

// motor vehicle object


class Motor {
make: string = ''
model: string = ''
color: string = ''
}

// create a new instance of each


const person1 = new Person()
const motor1 = new Motor()

// warning Property 'age' does not exist on type 'Motor'


motor1.age()

Abstraction.
Objects only reveal internal mechanisms that are relevant for the use of
other objects, hiding any unnecessary implementation code. This concept
helps developers more easily make changes and additions over time.

This is quite powerful in development and TypeScript provides us several


ways of manupulating member visibilty in a class object.

Let us see how this work. In this example we have added public in all the
Person attributes. By default all attributes are always public but for
readability it is good practice to add it.

// person class object


class Person {
public name: string = ''
public age: string = ''

public greetings(){
return name + ' ' + age
}
}

We can use the following to control attribute visibilities:

 Public
 Protected
 Private
 Static

Further we can use readonly which will prevents assignments to the field
outside of the constructor.
Let us take another example to further understand this concept.

// class person

class Person {
private readonly credentials: string = ''
private name: string = ''
private department: string = ''

constructor(value: string){
this.credentials = value
}

public setName(name: string): void {


if(!this.credentials) return
this.name = name
// logic get department
const userDepartment = axios.get(.....)
if(userDepartment) this.department = userDepartment
}

public getName(){
return `Employee name: ${this.name}, Department: $
{this.department}`
}
}

In the example above if we try to create a new instance of Person without


providing credentials we are going to get a warning and it wont compile.
The first thing we should notice is credentials visibility is private
readonly meaning it can only be updated within the constructor.

// warining An argument for 'value' was not provided.


const person1 = new Person()

// this will succeed


const person1 = new Person('123456')

One more thing we should not is all our attributes are private and cannot
be accessed outside the class object. Take note that we can only access
public objects outside the class object.

Try this

// this will succeed


const person1 = new Person('123456')
person1. // this will automatically list for you all the properties accessible
and you will NOTE all the private attributes cannot be accessed outside
the class object.

Inheritance.
Relationships and subclasses between objects can be assigned, allowing
developers to reuse a common logic while still maintaining a unique
hierarchy. This property of OOP forces a more thorough data analysis,
reduces development time and ensures a higher level of accuracy.

Take a look at this example. You will NOTE that by extending the
TeslaCompnany we have inherited all the public attributes and can call
them when we create a new instance of TeslaEmployee. This can allow us
have a base class and reuse the base class in different subsclasses.

// class company
type Department = {
name: string
}

type Employee = {
name: string
age: number
}

class TeslaCompany {
private static role = "Admin"
private readonly credentials: string = ''
private departments: (Department)[] = []
private employees: (Employee)[] = []

constructor(cred: string) {
this.credentials = cred
}

addDepartment(value: Department) {
this.departments = [...this.departments, value]
}

addEmployee(value: Employee) {
this.employees = [...this.employees, value]
}
}

class TeslaEmployee extends TeslaCompany {


private new_employee: Employee = { name: '', age: 0}

public setName(name: Employee): void {


this.new_employee = name

}
}

const newTeslaEmployee = new TeslaEmployee('123456')


newTeslaEmployee.setName({ name: 'Kevin Odongo', age: 36 })
newTeslaEmployee.addDepartment({ name: 'Finance' })
newTeslaEmployee.addEmployee({ name: 'Kevin Odongo', age: 36 })
console.log(newTeslaEmployee)

Polymorphism.
Objects can take on more than one form depending on the context. The
program will determine which meaning or usage is necessary for each
execution of that object, cutting down the need to duplicate code.

Let us understand this by going through an example. We will notice that


the Person object is extended by two different subsclasses and take
different context when each a new instance of each subsclass is created.

// class Person
class Person {
public name: string = ''
public role: string = ''
}

class basketballPlayer extends Person {


public setName(name: string){
this.name = name
this.role = 'BasketBall Player'
}
public getName(){
return `User name: ${this.name} Role: ${this.role}`
}
}

class golfPlayer extends Person {


public setName(name: string){
this.name = name
this.role = 'Golf Player'
}
public getName(){
return `User name: ${this.name} Role: ${this.role}`
}
}

const person1 = new basketballPlayer()


const person2 = new golfPlayer()
person1.setName('Kevin Odongo')
person2.setName('Kevin Odongo')
console.log(person1.getName())
console.log(person2.getName())

Functions

With functions, TypeScript infers the types of its parameters as


well as any return value.

Function Parameters
The types of function parameters work similarly to variable
declarations. If the parameter has a default value, it will take on
the type of that value. Otherwise, we may declare the type of that
parameter by adding a type annotation after its name.

Here, the logAgeAndName() function explicitly declares age to be of


type number, while name is inferred to be of type string from its
default value:

function logAgeAndName(age: number, name = '(anonymous)') {


console.log(`${name}, age ${age}`);
}

logAgeAndName(16, 'Mulan'); // ok: Mulan, age 16

logAgeAndName(0); // also ok: (anonymous), age 0

logAgeAndName('Mulan', 16);
// Argument of type 'string' is not assignable to parameter of type 'number'

The last use of logAgeAndName() shows that the order of the


parameters passed to the function matter. The first argument is
expected to be of type number while the second is inferred to be of
type string.

Optional Parameters
Function parameters can be made optional by adding a ? question
mark after their name, before any type annotation. TypeScript will
understand that they don’t need to be provided when the function
is invoked. Additionally, their type is a union that
includes undefined. This means that if a given function does not use
the optional parameter, its value is set to undefined.

The following logFavoriteNumberAndReason() function


indicates favorite as a required number and reason as an
optional string, so the function must be called with at least
one number parameter:

function logFavoriteNumberAndReason(favorite: number, reason?: string) {


console.log(`Favorite: ${favorite}!`);

if (reason) {
console.log(`Because: ${reason}!`);
}
}

logFavoriteNumberAndReason(7, 'an esoteric video game reference'); // Ok


logFavoriteNumberAndReason(9001); // Ok

logFavoriteNumberAndReason();
// Error: Expected 1-2 arguments, but got 0.

Return Types
Most functions are written in a way that TypeScript can infer what
value they return by looking at all the return statements in the
function. Functions that don’t return a value are considered to
have a return type of void.

In this example, the getRandomFriend() function is inferred to return


a string type because all return statements have a value of
type string, including the fallback parameter:

function getRandomFriend(fallback: string) {


switch (Math.floor(Math.random() * 5)) {
case 0:
return 'Josh';
case 1:
return 'Sara';
case 2:
return 'Sonny';
default:
return fallback;
}
}

const friend = getRandomFriend('Codey'); // Type: string

The return type of a function can be declared by adding a type


annotation after the ) right parenthesis following its list of
parameters. Doing so can be useful in two situations:

 We can make sure the function returns that type.


 TypeScript will not attempt to infer the return type of a
recursive function.

The following recursive fibonacci() function needs an explicit :


number return type annotation for TypeScript to understand it. This
returns type number:

function fibonacci(i: number): number {


if (i <= 1) {
return i;
}

return fibonacci(i - 1) + fibonacci(i - 2);


}

Function Types
Function types may be represented in the type system. They look
a lot like an arrow lambda, but with the return type instead of the
function body.

This withIncrementedValue() takes in a receiveNewValue parameter


function that itself takes in a number and returns nothing (void).
let value = 0;

function withIncrementedValue(receiveNewValue: (newValue: number) => void) {


value += 1;
receiveNewValue(value);
}

withIncrementedValue((receivedValue) => {
console.log('Got', receivedValue);
});

Function parameter types may be inferred if their parent function


is in a location with a known function type. In the prior example,
the receivedValue parameter was inferred to be type number.

Generics

Contribute to Docs

In TypeScript, generics are used to assign multiple types to a


function or variable without the value losing that specific type
information upon return. The any keyword is similar in that it
accommodates any and all types. However, it will not retain
specific type information.

Syntax
Generics are defined with < > brackets surrounding name(s) of
the generic type(s), like Array<T> or Map<Key, Value>.
interface MyType<GenericValue> {
value: GenericValue;
}

let myVar: MyType<string> = { value: 'Hello, World!' };


Generic types can be used with the following:

 Classes
 Functions
 Interfaces
 Type aliases

Example of Using Generics


In the following snippet, Box<Value> is a generic interface that with a
generic Value type within. Next, two
variables, numberBox and stringBox, are explicitly declared to be of
type Box<number> and Box<string>, respectively:

interface Box<Value> {
value: Value;
}

let numberBox: Box<number> = { value: 7 };


let stringBox: Box<string> = { value: 'Marathon' };

Interfaces
Contribute to Docs

Interfaces are used to “shape” an object by describing a certain


set of members and/or type annotations.

Syntax
Interfaces may be declared by:

1. Starting with the interface keyword.


2. Giving the interface a name.
3. Creating an object that contains a set of members and/or
type annotations.

interface myType {
memberOne: string;
memberTwo: number;
};
let myVar: myType = {"My favorite number is ", 42 };

Dog Interface Example


In this example, the Dog interface declares fluffy and woof members.
Any value declared to be of type Dog is therefore known to have
those members:

interface Dog {
fluffy: boolean;
woof(): string;
}

function interactWithDog(dog: Dog) {


dog.woof();

if (dog.fluffy) {
console.log('What a floof!');
}

dog.bark();
// Error: Property 'bark' does not exist on type 'Dog'.
}

Members that do not exist in the interface, such as bark(), cannot


be accessed and will throw a type error

Optional Members
Here, the Pet interface uses ? to set name as an optional member.
The only member that is required is species. Declaring an object of
type Pet doesn’t need a name but does need a species:

interface Pet {
name?: string;
species: string;
}

let anonymous: Pet = { // Ok


species: "Dog";
};

let named: Pet = {


name: "Emerald",
species: "Budgie",
};

let invalid: Pet = {


name: "My Rock",
}
// Error: Property 'species' is missing in type
// '{ name: string; }' but required in type 'Pet'.

Interface Extensions
Interfaces may be marked as extending another interface. Doing
so indicates that the derived child interface (the interface
extending others) includes all members from the base parent
interfaces (the interface being extended).

To mark an interface as extending other(s), add


the extends keyword after its name followed by any number of
interfaces to extend, with , commas between interface names.

In this example, the Cat interface is given a .walk() method by being


an extension of the Animal interface. However, instance of
type Animal don’t have access to members and methods defined in
the Cat interface:

interface Animal {
walk(): void;
}

interface Cat extends Animal {


fluffy: boolean;
purr(): string;
}

function workWithAnimals(animal: Animal, cat: Cat) {


animal.walk(); // Ok: defined on Animal
cat.walk(); // Ok: Cat extends Animal

if (cat.fluffy) {
// Ok: defined on Cat
console.log('Floof!!');
cat.purr(); // Ok: defined on Cat
}

animal.purr();
// Error: Property 'purr' does not exist on type 'Animal'.
}

Iterators and Generators

Iterables
An object is deemed iterable if it has an implementation for the Symbol.iterator property.
Some built-in types like Array, Map, Set, String, Int32Array, Uint32Array, etc. have
their Symbol.iterator property already implemented. Symbol.iterator function on an object is
responsible for returning the list of values to iterate on.

Iterable interface

Iterable is a type we can use if we want to take in types listed above which are iterable. Here
is an example:

function toArray<X>(xs: Iterable<X>): X[] {


return [...xs]
}

for..of statements

for..ofloops over an iterable object, invoking the Symbol.iterator property on the object.
Here is a simple for..of loop on an array:
let someArray = [1, "string", false];
for (let entry of someArray) {
console.log(entry); // 1, "string", false
}

for..of vs. for..in statements

Both for..of and for..in statements iterate over lists; the values iterated on are different
though, for..in returns a list of keys on the object being iterated, whereas for..of returns a list
of values of the numeric properties of the object being iterated.

Here is an example that demonstrates this distinction:

let list = [4, 5, 6];


for (let i in list) {
console.log(i); // "0", "1", "2",
}
for (let i of list) {
console.log(i); // 4, 5, 6
}

Another distinction is that for..in operates on any object; it serves as a way to inspect
properties on this object. for..of on the other hand, is mainly interested in values of iterable
objects. Built-in objects like Map and Set implement Symbol.iterator property allowing
access to stored values.

let pets = new Set(["Cat", "Dog", "Hamster"]);


pets["species"] = "mammals";
for (let pet in pets) {
console.log(pet); // "species"
}
for (let pet of pets) {
console.log(pet); // "Cat", "Dog", "Hamster"
}

Code generation

Targeting ES5 and ES3

When targeting an ES5 or ES3-compliant engine, iterators are only allowed on values
of Array type. It is an error to use for..of loops on non-Array values, even if these non-Array
values implement the Symbol.iterator property.

The compiler will generate a simple for loop for a for..of loop, for instance:
let numbers = [1, 2, 3];
for (let num of numbers) {
console.log(num);
}

will be generated as:

var numbers = [1, 2, 3];


for (var _i = 0; _i < numbers.length; _i++) {
var num = numbers[_i];
console.log(num);
}

Enums
Enums are one of the few features TypeScript has which is not a type-level extension of
JavaScript.

Enums allow a developer to define a set of named constants. Using enums can make it easier
to document intent, or create a set of distinct cases. TypeScript provides both numeric and
string-based enums.

Numeric enums
We’ll first start off with numeric enums, which are probably more familiar if you’re coming
from other languages. An enum can be defined using the enum keyword.

enum Direction {
Up = 1,
Down,
Left,
Right,
}
Try

Above, we have a numeric enum where Up is initialized with 1. All of the following members
are auto-incremented from that point on. In other words, Direction.Up has the
value 1, Down has 2, Left has 3, and Right has 4.

If we wanted, we could leave off the initializers entirely:

enum Direction {
Up,
Down,
Left,
Right,
}
Try

Here, Up would have the value 0, Down would have 1, etc. This auto-incrementing behavior
is useful for cases where we might not care about the member values themselves, but do care
that each value is distinct from other values in the same enum.

Using an enum is simple: just access any member as a property off of the enum itself, and
declare types using the name of the enum:

enum UserResponse {
No = 0,
Yes = 1,
}

function respond(recipient: string, message: UserResponse): void {


// ...
}

respond("Princess Caroline", UserResponse.Yes);


Try

Numeric enums can be mixed in computed and constant members (see below). The short
story is, enums without initializers either need to be first, or have to come after numeric
enums initialized with numeric constants or other constant enum members. In other words,
the following isn’t allowed:

enum E {
A = getSomeValue(),
B,
Enum member must have initializer.Enum member must have
initializer.
}
Try

String enums
String enums are a similar concept, but have some subtle runtime differences as documented
below. In a string enum, each member has to be constant-initialized with a string literal, or
with another string enum member.

enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
Try

While string enums don’t have auto-incrementing behavior, string enums have the benefit
that they “serialize” well. In other words, if you were debugging and had to read the runtime
value of a numeric enum, the value is often opaque - it doesn’t convey any useful meaning on
its own (though reverse mapping can often help). String enums allow you to give a
meaningful and readable value when your code runs, independent of the name of the enum
member itself.

Heterogeneous enums
Technically enums can be mixed with string and numeric members, but it’s not clear why you
would ever want to do so:

enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
Try

Unless you’re really trying to take advantage of JavaScript’s runtime behavior in a clever
way, it’s advised that you don’t do this.

Computed and constant members


Each enum member has a value associated with it which can be either constant or computed.
An enum member is considered constant if:

 It is the first member in the enum and it has no initializer, in which case it’s assigned
the value 0:

// E.X is constant:
enum E {
X,
}
Try

 It does not have an initializer and the preceding enum member was
a numeric constant. In this case the value of the current enum member will be the
value of the preceding enum member plus one.
// All enum members in 'E1' and 'E2' are constant.

enum E1 {
X,
Y,
Z,
}

enum E2 {
A = 1,
B,
C,
}
Try

 The enum member is initialized with a constant enum expression. A constant enum
expression is a subset of TypeScript expressions that can be fully evaluated at
compile time. An expression is a constant enum expression if it is:
1. a literal enum expression (basically a string literal or a numeric literal)
2. a reference to previously defined constant enum member (which can
originate from a different enum)
3. a parenthesized constant enum expression
4. one of the +, -, ~ unary operators applied to constant enum expression
5. +, -, *, /, %, <<, >>, >>>, &, |, ^ binary operators with constant enum
expressions as operands

It is a compile time error for constant enum expressions to be evaluated


to NaN or Infinity.

In all other cases enum member is considered computed.

enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}
Try

Union enums and enum member types


There is a special subset of constant enum members that aren’t calculated: literal enum
members. A literal enum member is a constant enum member with no initialized value, or
with values that are initialized to

 any string literal (e.g. "foo", "bar", "baz")


 any numeric literal (e.g. 1, 100)
 a unary minus applied to any numeric literal (e.g. -1, -100)

When all members in an enum have literal enum values, some special semantics come into
play.

The first is that enum members also become types as well! For example, we can say that
certain members can only have the value of an enum member:

enum ShapeKind {
Circle,
Square,
}

interface Circle {
kind: ShapeKind.Circle;
radius: number;
}

interface Square {
kind: ShapeKind.Square;
sideLength: number;
}

let c: Circle = {
kind: ShapeKind.Square,
Type 'ShapeKind.Square' is not assignable to type
'ShapeKind.Circle'.Type 'ShapeKind.Square' is not assignable to type
'ShapeKind.Circle'.
radius: 100,
};
Try

The other change is that enum types themselves effectively become a union of each enum
member. With union enums, the type system is able to leverage the fact that it knows the
exact set of values that exist in the enum itself. Because of that, TypeScript can catch bugs
where we might be comparing values incorrectly. For example:

enum E {
Foo,
Bar,
}

function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
This comparison appears to be unintentional because the types 'E.Foo'
and 'E.Bar' have no overlap.This comparison appears to be
unintentional because the types 'E.Foo' and 'E.Bar' have no overlap.
//
}
}
Try

In that example, we first checked whether x was not E.Foo. If that check succeeds, then
our || will short-circuit, and the body of the ‘if’ will run. However, if the check didn’t
succeed, then x can only be E.Foo, so it doesn’t make sense to see whether it’s not equal
to E.Bar.

Enums at runtime
Enums are real objects that exist at runtime. For example, the following enum

enum E {
X,
Y,
Z,
}
Try

can actually be passed around to functions

enum E {
X,
Y,
Z,
}

function f(obj: { X: number }) {


return obj.X;
}

// Works, since 'E' has a property named 'X' which is a number.


f(E);
Try
Enums at compile time
Even though Enums are real objects that exist at runtime, the keyof keyword works
differently than you might expect for typical objects. Instead, use keyof typeof to get a Type
that represents all Enum keys as strings.

enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG,
}

/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;

function printImportant(key: LogLevelStrings, message: string) {


const num = LogLevel[key];
if (num <= LogLevel.WARN) {
console.log("Log level key is:", key);
console.log("Log level value is:", num);
console.log("Log level message is:", message);
}
}
printImportant("ERROR", "This is a message");
Try

Reverse mappings

In addition to creating an object with property names for members, numeric enums members
also get a reverse mapping from enum values to enum names. For example, in this example:

enum Enum {
A,
}

let a = Enum.A;
let nameOfA = Enum[a]; // "A"
Try

TypeScript compiles this down to the following JavaScript:


"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
let a = Enum.A;
let nameOfA = Enum[a]; // "A"

Try

In this generated code, an enum is compiled into an object that stores both forward (name -
> value) and reverse (value -> name) mappings. References to other enum members are
always emitted as property accesses and never inlined.

Keep in mind that string enum members do not get a reverse mapping generated at all.

const enums

In most cases, enums are a perfectly valid solution. However sometimes requirements are
tighter. To avoid paying the cost of extra generated code and additional indirection when
accessing enum values, it’s possible to use const enums. Const enums are defined using
the const modifier on our enums:

const enum Enum {


A = 1,
B = A * 2,
}
Try

Const enums can only use constant enum expressions and unlike regular enums they are
completely removed during compilation. Const enum members are inlined at use sites. This is
possible since const enums cannot have computed members.

Decorators
A Decorator is a special kind of declaration that can be attached
to a class declaration, method, accessor, property, or parameter.
Decorators use the form @expression, where expression must evaluate
to a function that will be called at runtime with information about
the decorated declaration.

For example, given the decorator @sealed we might write


the sealed function as follows:
function sealed(target) {
// do something with 'target' ...
}

Decorator Factories
If we want to customize how a decorator is applied to a
declaration, we can write a decorator factory. A Decorator
Factory is simply a function that returns the expression that will
be called by the decorator at runtime.

We can write a decorator factory in the following fashion:

function color(value: string) {


// this is the decorator factory, it sets up
// the returned decorator function
return function (target) {
// this is the decorator
// do something with 'target' and 'value'...
};
}

Decorator Composition
Multiple decorators can be applied to a declaration, for example
on a single line:

@f @g x
Try

On multiple lines:

@f
@g
x
Try

When multiple decorators apply to a single declaration, their


evaluation is similar to function composition in mathematics. In

composite (f ∘ g)(x) is equivalent to f(g(x)).


this model, when composing functions f and g, the resulting
As such, the following steps are performed when evaluating
multiple decorators on a single declaration in TypeScript:

1. The expressions for each decorator are evaluated top-to-


bottom.
2. The results are then called as functions from bottom-to-top.

If we were to use decorator factories, we can observe this


evaluation order with the following example:

function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("first(): called");
};
}

function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("second(): called");
};
}

class ExampleClass {
@first()
@second()
method() {}
}
Try

Which would print this output to the console:

first(): factory evaluated


second(): factory evaluated
second(): called
first(): called

Decorator Evaluation
There is a well defined order to how decorators applied to various
declarations inside of a class are applied:
1. Parameter Decorators, followed by Method, Accessor,
or Property Decorators are applied for each instance
member.
2. Parameter Decorators, followed by Method, Accessor,
or Property Decorators are applied for each static member.
3. Parameter Decorators are applied for the constructor.
4. Class Decorators are applied for the class.

Class Decorators
A Class Decorator is declared just before a class declaration. The
class decorator is applied to the constructor of the class and can
be used to observe, modify, or replace a class definition. A class
decorator cannot be used in a declaration file, or in any other
ambient context (such as on a declare class).

The expression for the class decorator will be called as a function


at runtime, with the constructor of the decorated class as its only
argument.

If the class decorator returns a value, it will replace the class


declaration with the provided constructor function.

NOTE Should you choose to return a new constructor function, you must
take care to maintain the original prototype. The logic that applies
decorators at runtime will not do this for you.

The following is an example of a class decorator ( @sealed) applied


to a BugReport class:

@sealed
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}
}
Try

We can define the @sealed decorator using the following function


declaration:
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}

When @sealed is executed, it will seal both the constructor and its
prototype, and will therefore prevent any further functionality
from being added to or removed from this class during runtime by
accessing BugReport.prototype or by defining properties
on BugReport itself (note that ES2015 classes are really just
syntactic sugar to prototype-based constructor functions). This
decorator does not prevent classes from sub-classing BugReport.

Next we have an example of how to override the constructor to


set new defaults.

function reportableClassDecorator<T extends { new (...args: any[]): {}


}>(constructor: T) {
return class extends constructor {
reportingURL = "http://www...";
};
}

@reportableClassDecorator
class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}
}

const bug = new BugReport("Needs dark mode");


console.log(bug.title); // Prints "Needs dark mode"
console.log(bug.type); // Prints "report"

// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;
Property 'reportingURL' does not exist on type 'BugReport'.Property
'reportingURL' does not exist on type 'BugReport'. Try
Method Decorators
A Method Decorator is declared just before a method declaration.
The decorator is applied to the Property Descriptor for the
method, and can be used to observe, modify, or replace a method
definition. A method decorator cannot be used in a declaration
file, on an overload, or in any other ambient context (such as in
a declare class).

The expression for the method decorator will be called as a


function at runtime, with the following three arguments:

1. Either the constructor function of the class for a static


member, or the prototype of the class for an instance
member.
2. The name of the member.
3. The Property Descriptor for the member.

The NOTE Property Descriptor will be undefined if your script target is less
than ES5.

If the method decorator returns a value, it will be used as


the Property Descriptor for the method.

The return value is ignored if your script target is less than NOTE ES5.

The following is an example of a method decorator ( @enumerable)


applied to a method on the Greeter class:

class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}

@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
Try

We can define the @enumerable decorator using the following


function declaration:
function enumerable(value: boolean) {
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
descriptor.enumerable = value;
};
}
Try

The @enumerable(false) decorator here is a decorator factory. When


the @enumerable(false) decorator is called, it modifies
the enumerable property of the property descriptor.

Accessor Decorators
An Accessor Decorator is declared just before an accessor
declaration. The accessor decorator is applied to the Property
Descriptor for the accessor and can be used to observe, modify,
or replace an accessor’s definitions. An accessor decorator cannot
be used in a declaration file, or in any other ambient context
(such as in a declare class).

TypeScript disallows decorating both the NOTE get and set accessor for a
single member. Instead, all decorators for the member must be applied to
the first accessor specified in document order. This is because decorators
apply to a Property Descriptor, which combines both
the get and set accessor, not each declaration separately.

The expression for the accessor decorator will be called as a


function at runtime, with the following three arguments:

1. Either the constructor function of the class for a static


member, or the prototype of the class for an instance
member.
2. The name of the member.
3. The Property Descriptor for the member.

The NOTE Property Descriptor will be undefined if your script target is less
than ES5.

If the accessor decorator returns a value, it will be used as


the Property Descriptor for the member.

The return value is ignor NOTEed if your script target is less than ES5.
The following is an example of an accessor decorator ( @configurable)
applied to a member of the Point class:

class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}

@configurable(false)
get x() {
return this._x;
}

@configurable(false)
get y() {
return this._y;
}
}
Try

We can define the @configurable decorator using the following


function declaration:

function configurable(value: boolean) {


return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
descriptor.configurable = value;
};
}

Property Decorators
A Property Decorator is declared just before a property
declaration. A property decorator cannot be used in a declaration
file, or in any other ambient context (such as in a declare class).

The expression for the property decorator will be called as a


function at runtime, with the following two arguments:
1. Either the constructor function of the class for a static
member, or the prototype of the class for an instance
member.
2. The name of the member.

A NOTE Property Descriptor is not provided as an argument to a


property decorator due to how property decorators are initialized in
TypeScript. This is because there is currently no mechanism to describe
an instance property when defining members of a prototype, and no way
to observe or modify the initializer for a property. The return value is
ignored too. As such, a property decorator can only be used to observe
that a property of a specific name has been declared for a class.

We can use this information to record metadata about the


property, as in the following example:

class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}

We can then define the @format decorator and getFormat functions


using the following function declarations:

import "reflect-metadata";
const formatMetadataKey = Symbol("format");
function format(formatString: string) {
return Reflect.metadata(formatMetadataKey, formatString);
}
function getFormat(target: any, propertyKey: string) {
return Reflect.getMetadata(formatMetadataKey, target, propertyKey);
}

The @format("Hello, %s") decorator here is a decorator factory.


When @format("Hello, %s") is called, it adds a metadata entry for the
property using the Reflect.metadata function from the reflect-
metadata library. When getFormat is called, it reads the metadata
value for the format.
This example requires the NOTE reflect-metadata library. See Metadata for
more information about the reflect-metadata library.

Parameter Decorators
A Parameter Decorator is declared just before a parameter
declaration. The parameter decorator is applied to the function for
a class constructor or method declaration. A parameter decorator
cannot be used in a declaration file, an overload, or in any other
ambient context (such as in a declare class).

The expression for the parameter decorator will be called as a


function at runtime, with the following three arguments:

1. Either the constructor function of the class for a static


member, or the prototype of the class for an instance
member.
2. The name of the member.
3. The ordinal index of the parameter in the function’s
parameter list.

A parameter decorator can only be used to observe that a parameter has


been declared on a method. NOTE

The return value of the parameter decorator is ignored.

The following is an example of a parameter decorator ( @required)


applied to parameter of a member of the BugReport class:

class BugReport {
type = "report";
title: string;

constructor(t: string) {
this.title = t;
}

@validate
print(@required verbose: boolean) {
if (verbose) {
return `type: ${this.type}\ntitle: ${this.title}`;
} else {
return this.title;
}
}
}
Try

We can then define the @required and @validate decorators using the
following function declarations:

import "reflect-metadata";
const requiredMetadataKey = Symbol("required");

function required(target: Object, propertyKey: string | symbol,


parameterIndex: number) {
let existingRequiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyKey) ||
[];
existingRequiredParameters.push(parameterIndex);
Reflect.defineMetadata( requiredMetadataKey,
existingRequiredParameters, target, propertyKey);
}

function validate(target: any, propertyName: string, descriptor:


TypedPropertyDescriptor<Function>) {
let method = descriptor.value!;

descriptor.value = function () {
let requiredParameters: number[] =
Reflect.getOwnMetadata(requiredMetadataKey, target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length ||
arguments[parameterIndex] === undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
Try

The @required decorator adds a metadata entry that marks the


parameter as required. The @validate decorator then wraps the
existing print method in a function that validates the arguments
before invoking the original method.

This example requires the NOTE reflect-metadata library. See Metadata for
more information about the reflect-metadata library.

You might also like