0% found this document useful (0 votes)
3 views19 pages

Typescript Notes

The document provides an overview of TypeScript, covering variable types, enums, type assertions, type guards, union and intersection types, literal types, collection types, interfaces, and functions. It explains how to define and use these features, including examples of each concept. Additionally, it discusses classes and static properties in TypeScript, emphasizing the importance of type safety and structure in programming.

Uploaded by

jaloga83
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views19 pages

Typescript Notes

The document provides an overview of TypeScript, covering variable types, enums, type assertions, type guards, union and intersection types, literal types, collection types, interfaces, and functions. It explains how to define and use these features, including examples of each concept. Additionally, it discusses classes and static properties in TypeScript, emphasizing the importance of type safety and structure in programming.

Uploaded by

jaloga83
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 19

Typescript

Types of variables:

Primitives:

 boolean;
 number;
 string;
 void, null, undefined;
 Function
 array

OtherTypes:

 Any
 unknown

Enums: An enum, is a symbolic name for a set of values. Enumerations are treated as data types, and
you can use them to create sets of constants for use with variables and properties.

enum ContractStatus {
Permanent,
Temp,
Apprentice
}

let employeeStatus: ContractStatus = ContractStatus.Temp;


console.log(employeeStatus);
By default, enum values begin with a value of 0, so Permanent is 0, Temp is 1,
and Apprentice is 2. If you want the values to start with a different value, in
this case 1, specify that in the enum declaration. Make the following edits to
have the enum start the values at 1.

enum ContractStatus {
Permanent = 1,
Temp,
Apprentice
}

Type assertion ("trust me, I know what I’m doing.")


Type assertions have two forms. One is the as-syntax:
(randomValue as string).toUpperCase();
The other version is the "angle-bracket" syntax:
(<string>randomValue).toUpperCase();

Type guards (using typeof in predicates to validate type)


Example:
string typeof s ===
"string"

Union Types ( '|' operator )


A union type describes a value that can be one of several types. This can be helpful
when a value is not under your control (for example, values from a library, an API,
or user input.)

Examples:

Example1:

let multiType: number | boolean;

multiType = 20; //* Valid


multiType = true; //* Valid
multiType = "twenty"; //* Invalid

Example2:

functionadd(x: number| string, y: number| string) {


if (typeof x === 'number' && typeof y === 'number') {
return x + y;
}
if (typeof x === 'string' && typeof y === 'string') {
return x.concat(y);
}
throw new Error('Parameters must be numbers or strings');
}
console.log(add('one', 'two')); //* Returns "onetwo"
console.log(add(1, 2)); //* Returns 3
console.log(add('one', 2)); //* Returns error

Intersection types ( '&' Operator )


Intersection types are closely related to union types, but they are used very
differently. An intersection type combines two or more types to create a new type
that has all properties of the existing types. This allows you to add together existing
types to get a single type that has all the features you need.

interface Employee {
employeeID: number;
age: number;
}
interface Manager {
stockPlan: boolean;
}
type ManagementEmployee = Employee & Manager;

let newManager: ManagementEmployee = {


employeeID: 12345,
age: 34,
stockPlan: true
};

Literal types
A literal is a more concrete subtype of a collective type. What this means is
that "Hello World" is a string, but a string is not "Hello World" inside the type
system.
There are three sets of literal types available in TypeScript: string, number,
and boolean. By using literal types, you can specify an exact value that a
string, number, or boolean must have (for example, "yes", "no", or "maybe".)

LITERAL NARROWING: Using const instead of let or var. Shrinks the scope of
the variable since it will either change whenever either to never change. This
process is called literal narrowing (reducing to a finite number of use cases
by changing literals)

Defining literal types


Literal types are written as object, array, function, or constructor type literals and are used to
compose new types from other types.

Example1:

type testResult = "pass" | "fail" | "incomplete";


let myResult: testResult;
myResult = "incomplete"; //* Valid
myResult = "pass"; //* Valid
myResult = "failure"; //* Invalid

Example2:
type dice = 1 | 2 | 3 | 4 | 5 | 6;
let diceRoll: dice;
diceRoll = 1; //* Valid
diceRoll = 2; //* Valid
diceRoll = 7; //* Invalid

Collection Types
Arrays

TypeScript, like JavaScript, allows you to work with arrays. Arrays can be
written in one of two ways. In the first, you use the type of the elements
followed by square brackets ([ ]) to denote an array of that element type:

let list: number[] = [1, 2, 3];


The second way uses a generic Array type, using the syntax Array<type>:

let list: Array<number> = [1, 2, 3];

Tuples

Having an array of the same value types is useful, but sometimes you have
an array that contains values of mixed types. For that purpose, TypeScript
provides the Tuple type. To declare a Tuple, use the syntax variableName:
[type, type, ...].

let person1: [string, number] = ['Marcia', 35];


let person1: [string, number] = ['Marcia', 35, true];
You'll see that an error is raised because the elements in the Tuple array are
fixed. The person1 Tuple is an array that contains exactly one string value
and one numeric value.

let person1: [string, number] = [35, 'Marcia'];


You'll see an error that indicates that the order of the values must match the
order of the types.

INTERFACES
What is an interface
You can use interfaces to describe an object, naming and parameterizing the object's types,
and to compose existing named object types into new ones.
This simple interface defines the two properties and a method of an Employee object.

interface Employee {
firstName: string;
lastName: string;
fullName(): string;
}

Notice that the interface doesn't initialize or implement the properties declared within it.
That's because the only job of an interface is to describe a type.

let employee: Employee = {


firstName : "Emil",
lastName: "Andersson",
fullName(): string {
return this.firstName + " " + this.lastName;
}
}
employee.firstName = 10; //* Error - Type 'number' is not assignable to type 'string'

Reasons for using an interface in TypeScript


You can use an interface to:
 Create shorthand names for commonly used types. With even a simple interface like the
one declared in the earlier example, you still get the benefit of Intellisense and type
checking.

 Drive consistency across a set of objects because every object that implements the
interface operates under the same type definitions. This can be useful when you're
working with a team of developers and you want to ensure that proper values are being
passed into properties, constructors, or functions. For example, objects that implement an
interface must implement all the required members of the interface. So, if you don't pass
all the required parameters of the correct type, the TypeScript compiler will throw an
error.

 Describe existing JavaScript APIs and clarify function parameters and return types. This is
especially useful when you're working with JavaScript libraries like jQuery. An interface
can provide you with a clear understanding of what a function is expecting and what it
will return without repeat visits to the documentation.

How is an interface different from a type alias?


The Employee interface above can also be expressed as a type alias using the type keyword:

typeEmployee = {
firstName: string;
lastName: string;
fullName(): string;
}

A type alias is a definition of a type of data, for example, a union, primitive, intersection, tuple, or
any other type. Interfaces, on the other hand, are a way to describe data shapes, for example, an
object. Type aliases can act like interfaces; however, there are some subtle differences. The key
distinction is that a type alias cannot be reopened to add new properties whereas an interface is
always extendable.

Extend an interface
There are several types of desserts you can create from the IceCream interface (sundaes,
milkshakes, and so on), but they all have different properties in addition to those declared
in IceCream. Let's extend the interface with a new one called Sundae and declare its properties.

Create indexable types


You can use interfaces that describe array types that you can index into.
Indexable types have an index signature that describes the type you can use to index into the
object, along with the corresponding return types when indexing. (Block scope)

interface IceCreamArray {
[index: number]: string;
}
let myIceCream: IceCreamArray;
myIceCream = ['chocolate', 'vanilla', 'strawberry'];
let myStr: string = myIceCream[0];
console.log(myStr);

Describe a JavaScript API using an interface


A common pain point for JavaScript and TypeScript developers alike is working with external
JavaScript libraries. You can use an interface to describe existing JavaScript APIs and clarify
function parameters and return types. The interface provides you with a clear understanding of
what an API is expecting and what it will return.

The fetch API is a native JavaScript function that you can use to interact with web services. This
example declares an interface called Post for the return types in a JSON file and then
uses fetch with async and await to generate a strongly typed response.

const fetchURL = 'https://jsonplaceholder.typicode.com/posts' // Interface describing the


shape of our json data
interface Post {
userId: number;
id: number;
title: string;
body: string;
}

async function fetchPosts( url: string) {


let response = await fetch(url);
let body = await response.json();
return body as Post[];
}
async function showPost() {
let posts = await fetchPosts(fetchURL); // Display the contents of the first item in the response
let post = posts[0]; console.log('Post #' + post.id) // If the userId is 1, then display a note that it's
an administrator
console.log('Author: ' + (post.userId === 1 ? "Administrator" : post.userId.toString()))
console.log('Title: ' + post.title) console.log('Body: ' + post.body)
}
showPost();

Functions

Named functions
The syntax for declaring a named function in TypeScript is the same as defining one in JavaScript.
The only difference with TypeScript is that you can provide a type annotation for the function's
parameters and return value.

function addNumbers (x: number, y: number): number {


return x + y;
}
addNumbers(1, 2);

Anonymous functions
Function expressions represent values so they are usually assigned to a variable or passed to
other functions, and can be anonymous, meaning the function has no name.
This example assigns a function expression to the variable addNumbers. Notice that function
appears in place of the function name, making the function anonymous. You can now use this
variable to call the function.

let addNumbers = function (x: number, y: number): number {


return x + y;
}
addNumbers(1, 2);

Arrow functions
Arrow functions (also called Lambda or fat arrow functions because of the => operator used to
define them) provide shorthand syntax for defining an anonymous function. Due to their concise
nature, arrow functions are often used with simple functions and in some event handling
scenarios.
// Arrow function
let addNumbers2 = (x: number, y: number): number => x + y;

Required parameters
All function parameters are required, unless otherwise specified, and the number of arguments
passed to a function must match the number of required parameters the function expects.

function addNumbers (x: number, y: number): number {


return x + y;
}
Optional parameters
You can also define optional parameters by appending a question mark (?) to the end of the
parameter name.

function addNumbers (x: number, y?: number): number {


if (y === undefined) {
return x;
} else {
return x + y;
}
}

Default parameters
You can also assign default values to optional parameters. If a value is passed as an argument to
the optional parameter, that value will be assigned to it. Otherwise, the default value will be
assigned to it. As with optional parameters, default parameters must come after required
parameters in the parameter list.

function addNumbers (x: number, y = 25): number {


return x + y;
}

Rest Parameters
If you want to work with multiple parameters as a group (in an array) or don't know how many
parameters a function will ultimately take, you can use rest parameters. Rest parameters are
treated as a boundless number of optional parameters. You may leave them off or have as many
as you want.

let addAllNumbers = (firstNumber: number, ...restOfNumbers: number[]): number => {


let total: number = firstNumber;
for(let counter = 0; counter < restOfNumbers.length; counter++) {
if(isNaN(restOfNumbers[counter])){
continue;
}
total += Number(restOfNumbers[counter]);
}
return total;
}

Deconstructed object parameters


Function parameters are positional and must be passed in the order in which they are defined in
the function. This can make your code less-readable when calling a function with multiple
parameters which are optional or the same data type. To enable named parameters you can use
a technique called deconstructed object parameters. This enables you to use an interface to
defined named, rather than positional, parameters in your functions.
interface Message {
text: string;
sender: string;
}

function displayMessage({text, sender}: Message) {


console.log(`Message from ${sender}: ${text}`);
}

displayMessage({sender: 'Christopher', text: 'hello, world'});

Define function types


You can define a function type using a type alias or an interface. Both approaches work
essentially the same so it's up to you to decide which is best. An interface is a better if you want
to have the option of extending the function type. A type alias is better if you want to use unions
or tuples.

Example1:

type calculator = (x: number, y: number) => number;


let addNumbers: calculator = (x: number, y: number): number => x + y;
let subtractNumbers: calculator = (x: number, y: number): number => x - y;

// Alternative

let doCalculation = (operation: 'add' | 'subtract'): calculator => {


if (operation === 'add') {
return addNumbers;
} else {
return subtractNumbers;
}
}

// Interface for functions

interface Calculator {
(x: number, y: number): number; //Notation is slightly different
}

Function type inference


When defining a function, the names of the function parameters do not need to match those in
the function type. While you are required to name the parameters in the type signature in both
places, the names are ignored when checking if two function types are compatible.

let addNumbers: Calculator = (x: number, y: number): number => x + y;


let addNumbers: Calculator = (number1: number, number2: number): number => number1 +
number2;
let addNumbers: Calculator = (num1, num2) => num1 + num2;

Classes in Typescript
Example of Class with typed properties:

class Car {
constructor(make: string, color: string, doors = 4){
this._make = make;
this._color = color;
this._doors = doors;
Car.numberOfCars++; // Increments the value of the static property
}
//typed for class
private static numberOfCars: number = 0; // New static property
_make: string;
_color: string;
_doors: number;
get make() {
return this._make;

}
set make(make) {
this._make = make;
}
get color() {
return 'The color of the car is ' + this._color;
}
set color(color) {
this._color = color;
}
get doors() {
return this._doors;
}
set doors(doors) {
if ((doors % 2) === 0) {
this._doors = doors;
} else {
throw new Error('Doors must be an even number');
}
}
// Methods
accelerate(speed: number): string {
return `${this.worker()} is accelerating to ${speed} MPH.`
}
brake(): string {
return `${this.worker()} is braking with the standard braking system.`
}
turn(direction: 'left' | 'right'): string {
return `${this.worker()} is turning ${direction}`;
}
// This function performs work for the other method functions
worker(): string {
return this._make;
}
}

Static Properties:
o make a property static, use the static keyword before a property or
method name.
For example, you can add a new static property to the Car class
called numberOfCars that stores the number of times that the Car class
is instantiated and set its initial value to 0. Then, in the constructor,
increment the count by one.
Notice that you use the syntax className.propertyName instead
of this. when accessing the static property.
You can also define static methods. You can call
the getNumberOfCars method to return the value of numberOfCars.

public static getNumberOfCars(): number {


return Car.numberOfCars;
}

Override a method (Inheritance)


When a derived class has a different definition for one of the member
functions of the base class, the base function is said to be overridden.
Overriding is what happens when you create a function in a subclass
with the same name as the function in the base class but, it has different
functionality.
For example, assume that electric cars use a different type of braking
system than traditional cars called regenerative braking. So, you may
want to override the brake method in the Car base class with a method
that is specialized for the ElectricCar subclass.

Declare an interface to ensure class shape


You can use an interface to establish a "code contract" that describe
the required properties of an object and their types. So, you can use an
interface to ensure class instance shape. Class declarations may
reference one or more interfaces in their implements clause to validate
that they provide an implementation of the interfaces.

interface Vehicle {
make: string;
color: string;
doors: number;
accelerate(speed: number): string;
brake(): string;
turn(direction: 'left' | 'right'): string;
}
How to implement the interface within the class:

class Car implements Vehicle {


// ...
}

Design considerations
Interfaces are a TypeScript design-time construct. Because JavaScript
does not have a concept of interfaces, they are removed when
TypeScript is transpiled to JavaScript. This means they are completely
weightless, take up no space in the resulting file, and have no negative
impact on the code that will be executed.

Generics:
Generics are code templates that you can define and reuse throughout
your codebase. They provide a way to tell functions, classes, or
interfaces what type you want to use when you call it. You can think
about this in the same way that arguments are passed to a function,
except a generic enables you to tell the component what type it should
expect at the time it's called.

Create generic functions when your code is a function or class that:


 Works with a variety of data types.
 Uses that data type in several places.
Generics can:
 Provide more flexibility when working with types.
 Enable code reuse.
 Reduce the need to use the any type.

Example:
The getArray function generates an array of items of any type. First definition is:

 BAD: ❌
function getArray(items : any[]) : any[] {

return new Array().concat(items);


}

 Solved by using Generics:


 GOOD: ✅
function getArray<T>(items : T[]) : T[] {
return new Array<T>().concat(items);
}

Instantiation:

let numberArray = getArray<number>([5, 10, 15, 20]);


numberArray.push(25); // OK
numberArray.push('This is not a number'); // Generates a compile time type check
error

let stringArray = getArray<string>(['Cats', 'Dogs', 'Birds']);


stringArray.push('Rabbits'); // OK
stringArray.push(30); // Generates a compile time type check error

Using multiple type variables


You are not limited to using a single type variable in your generic
components.
For example, the identity function accepts two
parameters, value and message, and returns the value parameter. You
can use two generics, T and U, to assign different types to each
parameter and to the return type. The variable returnNumber is
initialized by calling the identity function with <number, string> as the
types for the value and message arguments, returnString is initialized by
calling it with <string, string>, and returnBoolean is initialized by calling
it with <boolean, string>. When using these variables, TypeScript can
type check the values and return a compile-time error if there is a
conflict.

function identity<T, U> (value: T, message: U) : T {


console.log(message);
return value
}

let returnNumber = identity<number, string>(100, 'Hello!');


let returnString = identity<string, string>('100', 'Hola!');
let returnBoolean = identity<boolean, string>(true, 'Bonjour!');

returnNumber = returnNumber * 100; // OK


returnString = returnString * 100; // Error: Type 'number' not assignable to type 'string'
returnBoolean = returnBoolean * 100; // Error: Type 'number' not assignable to type 'boolean'
Use the methods and properties of a generic
type
When using type variables to create generic components, you may only
use the properties and methods of objects that are available
for every type. This prevents errors from occurring when you try to
perform an operation on a parameter value that is incompatible with the
type that's being passed to it.

Take the example:

function identity<T, U> (value: T, message: U) : T {


let result: T = value + value; // Error
console.log(message);
return result
}

The identity function can accept any type that you choose to pass to the
type variables. But, in this case, you should constrain the types that
the value parameter can accept to a range of types that you can
perform an add operation on, rather than accepting any possible type.
This is called a generic constraint. There are several ways to do this
depending on the type variable. One way is to declare a custom type as
a tuple and then extend the type variable with the custom type. The
following example declares ValidTypes as a tuple with a string and
a number. Then, it extends T with the new type. Now, you can only
pass number or string types to the type variable.

type ValidTypes = string | number;


function identity<TextendsValidTypes, U> (value: T, message: U) : T {
let result: T = value + value; // Error
console.log(message);
return result
}

let returnNumber = identity<number, string>(100, 'Hello!'); // OK


let returnString = identity<string, string>('100', 'Hola!'); // OK
let returnBoolean = identity<boolean, string>(true, 'Bonjour!'); // Error: Type 'boolean' does not
satisfy the constraint 'ValidTypes'.

''
You can only use a typeof type guard to check the primitive
types string, number, bigint, function, boolean, symbol, object, and undefined. To check the type
of a class, use an instanceof type guard.
''
Implement generics with custom types and
classes
Using generics with primitive types, like number, string, or boolean,
illustrate the concepts of generics well, but the most powerful uses
come from using them with custom types and classes.This example has
a base class called Car and two subclasses, ElectricCar and Truck.
The accelerate function accepts a generic instance of Car and then
returns it. By telling the accelerate function that T must extend Car,
TypeScript knows which functions and properties you can call within the
function. The generic also returns the specific type of car
(ElectricCar or Truck) passed into the function, rather than a non-
specific Car object.

You can do this by defining an interface and then using


the extend keyword with the type variable to extend it. The previous
example constrained the T type by attaching a restriction to it – T must
extend Car.

class Car {
make: string = 'Generic Car';
doors: number = 4;
}

class ElectricCar extends Car {


make = 'Electric Car';
doors = 4
}

class Truck extends Car {


make = 'Truck';
doors = 2
}

function accelerate<T extends Car> (car: T): T {


console.log(`All ${car.doors}doors are closed.`);
console.log(`The ${car.make}is now accelerating!`)
return car
}

let myElectricCar = new ElectricCar; //"All 4 doors are closed."


accelerate<ElectricCar>(myElectricCar); // "The Electric Car is now accelerating!"
let myTruck = new Truck; // "All 2 doors are closed."
accelerate<Truck>(myTruck); // "The Truck is now accelerating!"

TypeScript namespaces
Namespaces (referred to as "internal modules" in earlier versions of
TypeScript) are a TypeScript-specific way to organize and categorize
your code, enabling you to group related code together. Namespaces
allow you to group variables, functions, interfaces, or classes related to
business rules in one namespace and security in another.

You can use namespaces to:


 Reduce the amount of code in the global scope, limiting "global scope
pollution."
 Provide a context for names, which helps reduce naming collisions.
 Enhance reusability.

Example"

namespace AllGreetings {

export namespace Greetings {


export function returnGreeting(greeting: string) {
console.log(`The message from namespace Greetings is ${greeting}.`);
}
}

export namespace GreetingsWithLength {


export function returnGreeting(greeting: string) {
let greetingLength = getLength(greeting);
console.log(`The message from namespace GreetingsWithLength is ${greeting}. It is $
{greetingLength}characters long.`);
}
function getLength(message: string): number{
return message.length
}
}
}

To call the functions, start by typing the outermost namespace


name, AllGreetings, a period, and then the next level in the namespace
hierarchy, Greetings or GreetingsWithLength. Continue down the
hierarchy until you reach the function name.

AllGreetings.Greetings.returnGreeting('Bonjour'); // OK
AllGreetings.GreetingsWithLength.returnGreeting('Hola'); // OK

Other Example:

import greet = AllGreetings.Greetings;


greet.returnGreeting('Bonjour');

Organize code by using multi-file


namespaces
You can extend namespaces by sharing them across multiple TypeScript
files. When you have namespaces in multiple files that relate to each
other, you must add reference tags to tell the TypeScript compiler about
the relationships between the files. For example, assume that you have
three Typescript files:

A good guideline example:

 interfaces.ts, which declares a namespace that contains some


interface definitions.
 functions.ts, which declares a namespace with functions that
implement the interfaces in interfaces.ts.
 main.ts, which calls the functions in functions.ts and represents the
main code of your application.

To inform TypeScript of the relationship


between interfaces.ts and functions.ts, you add
a reference to interfaces.ts using the triple slash (///) syntax to the top
of functions.ts. And then in main.ts, which has a relationship with
both interfaces.ts and functions.ts, you add a reference to both files.
TRADEOFF/COMPARISON BENEFITS MODULES VS NAMESPACES:

Module Namespace
Use modules to organize code into Use namespaces to organize code
separate files for logical grouping of into separate files for logical
functionality. grouping of functionality.
Modules execute in their local scope, not Namespaces execute in their local
in the global scope. scope, not in the global scope.
Modules are declarative; the Namespaces cannot declare their
relationships between modules are dependencies.
specified in terms of imports and exports
at the file level.
You define a module by using either You define a namespace by using
the export or import keyword within a file. the namespace keyword within a file.
Any file containing a top-level import or Namespace statements can be
export is considered a module. nested and span multiple files.
To expose individual module components To expose individual namespace
outside the module, use components outside of the
the export keyword. namespace, use
the export keyword.
To use a component from one module in To use a component from one
another module, use the import keyword. namespace in another TypeScript
file, add a reference statement using
the triple-slash (///) syntax.
To compile a module and all its To compile TypeScript files
dependent files, use the tsc -- containing namespaces and all
module command. their dependent files into individual
JavaScript files, use
the tsc command.
It is not possible to have multi-file To compile all TypeScript files
modules compiled to a single module. containing namespaces into a
single JavaScript file, use the tsc --
outFile command.

Modules import another module by using Namespaces are loaded by


a module loader API. You specify the API specifying the .js file names (in
when you compile the module. order) using the <script> tag in the
HTML page.
In modules, you can re-export the In namespaces, you cannot re-
components either using their original export components or rename
name or rename it. them.

You might also like