/typescript-basics

TypeScript Basics

Primary LanguageTypeScript

INSTALL TYPESCRIPT GLOBALLY

npm i typescript -g

LINK SCRIPT FILE

<script src="./app.js" type="text/javascript"></script>

ADD TS CODE

// app.ts
// WE USE TYPE ASSIGNMENT WHEN WE DECLARE PARAMETERS
function somma(a: number, b: number) {
    return a + b
};
console.log(somma(3, 5));

TS DATA TYPES

Basic data types:

  • String
  • Number
  • Boolean

Custom data types:

  • Array
let tsArray: [];
let tsArray2: array;
let tsArray3: string[];
let tsArray5: number[];
let tsArray6: any[];
  • Object
let tsObject: {};
let tsObject2: object;

 //๐Ÿ‘‡ Type 'string' is not assignable to type 'object'๐Ÿ‘‡
tsObject2 = 'mario'; // โŒ

// โœ”๏ธ OK
tsObject2 = {
    name: "Mario",
    age: 30,
    isMarried: false,
    address: {city: 'Roma', number: 30, street: 'Via Milano'}
};

// WE CAN DECLARE THE EXPECTED OBJECT STRUCTURE
let tsObject3: {
    name: string,
    age: number,
    isMarried: boolean
};

// WE CAN NOT CHANGE VALUE TYPES
tsObject3 = {
    name: "Mario",
    //๐Ÿ‘‡ Type 'string' is not assignable to type 'number' ๐Ÿ‘‡
    age: 'trenta', // โŒ
    isMarried: false
};

// WE CAN NOT ADD UNDECLARED PROPERTIES
tsObject3 = {
    name: "Mario",
    age: 30,
    isMarried: false,
    //๐Ÿ‘‡ Object literal may only specify known properties, and 'address' does not exist in type '{ name: string; age: number; isMarried: boolean; }'. ๐Ÿ‘‡
    address: {city: 'Roma', number: 30, street: 'Via Milano'} // โŒ
};

// WE CAN DECLARE AN EXPECTED OBJECT STRUCTURE ON A FUNCTION PARAMETER
function getData(data: {id: number; username: string; passwod: string}) {
    // WE CAN ACCESS THE PROPERTIES OF THE PARAMETERS
    console.log(`USER ID: ${data.id}`);
};
  • Tuple (fixed length array)
// IT WILL EXPECT AN ARRAY CONTAINING ONLY 2 VALUES TYPE NUMBER
let tuple : [number, number];

//๐Ÿ‘‡ Type 'string' is not assignable to type 'number'. ๐Ÿ‘‡
tuple = ['carl', 2]; // โŒ

//๐Ÿ‘‡ Type '[number, number, number]' is not assignable to type '[number, number]'. Source has 3 element(s) but target allows only 2. ๐Ÿ‘‡
tuple = [1, 2, 3]; // โŒ

Advanced data types:

  • Any
  • Void
  • Null
  • Undefined
  • Never
  • Union (used to assign multyple data types)
//๐Ÿ‘‡ Type 'boolean' is not assignable to type 'string | number'. ๐Ÿ‘‡
let unionTs: string | number = 'a string'; // โŒ

let unionTs2: string | number | string[]= 'a string';
  • Type Aliases (custom data types)
type Person = {
    name: string,
    age: number,
    isMarried: boolean
    address: {city: string, number: number, street: string}
};

let user: Person;

user = {
    name: 'Mario',
    age: 30,
    isMarried: false,
    address: {city: 'Roma', number: 30, street: 'Via Milano'}
};

type Test = string | number;

//๐Ÿ‘‡ Type 'boolean' is not assignable to type 'Test'. ๐Ÿ‘‡
let customTest: Test = true; // โŒ

// โœ”๏ธ
let customTest: Test = 'true';

// WE CAN DECLARE THE EXPECTED TYPE ON A FUNCTION PARAMETER
function getPerson(user: Person) {
    // WE CAN ACCESS THE PROPERTIES OF THE PARAMETERS
    console.log(`USER ID: ${user.name}`);
};
  • Enum
// WE CAN DECLARE THE EXPECTED VALUES LIST THANKS TO ENUM
enum Role {
    ADMIN,
    USER,
    GUEST
};

let enumUser = {
    name: 'Mario',
    age: 30,
    role: Role.ADMIN
}

// EXAMPLE OF USE
type User = {
    name: string,
    age: number,
    isMarried: boolean,
    address: {city: string, number: number, street: string},
    role: Role
};

let testUser1: User = {
    name: 'Mario',
    age: 30,
    isMarried: false,
    address: { city: 'Roma', number: 30, street: 'Via Milano'
    },
    role: Role.ADMIN
}

// ENUM ASSIGNS AN INDEX ON EACH ENTRY. WE CAN EDIT THIS INDEX AND ASSIGN IT TO A NEW NUMERIC VALUE OR STRING:
enum Role {
    ADMIN = 10,
    USER, // THIS WILL BE (enum member) Role.USER = 11
    GUEST = 'guest' // (enum member) Role.GUEST = "guest"
};

FUNCTIONS

// IF WE ASSIGN A DEFAULT VALUE TO A PARAMETER, TS INFERENCE WILL ASSIGN THE TYPE OF THAT VALUE TO THE PARAMETER. (parameter) b: number
function somma2(a: number, b = 3) {
    return a + b
};

IF WE CHECK somma2 WE WILL SEE THAT TS EXPECTS A NUMBER, A SECOND NUMBER (OPTIONAL AS IT AS A DEFAULT) AND WILL RETURN A NUMBER AS RESULT

function somma2(a: number, b?: number): number

WE CAN ALSO DECLARE A TYPE OF THE RETURN VALUE IF NEEDED

function tenCheck(a: number, b = 3): any {
    if ((a+b) > 10 ) {
        return 'sum is > 10'
    } else {
        return 'sum is <= 10'
    }
};

THE BEST WAY TO WRITE THIS FUNCTION SHOULD BE

function somma3(a: number, b: number = 3): string {
    if ((a + b) > 10) {
        return 'La somma รจ > 10';
    } else {
        return 'La somma รจ <= 10';
    }
}

IF WE WILL NOT RETURN A VALUE, WE CAN DECLARE A RETURN TYPE OF VOID

function somma3(a: number, b = 3): void {
    console.log(a+b);
};

THIS WAY IF WE DECLARE A RETURN NOT VOID TS WILL WARN US

function somma3(a: number, b = 3): void {
    //๐Ÿ‘‡ Type 'number' is not assignable to type 'void'. ๐Ÿ‘‡
    return a + b // โŒ
}

IF WE WANT TO ASSIGN A FUNCTION TO A VARIABLE, WE CAN ASSIGN THE TYPE FUNCTION AS WE DECLARE THE VARIABLE:

function somma3(a: number, b = 3): void {
    console.log(a+b);
};

let sommaVar: Function = somma3

WHEN ASSIGNING FUNCTIONS TO VARIABLES WE CAN SPECIFY WHAT KIND OF PARAMETERS AND RETURN WE ACCEPT

let sommaVar: (a: number, b: number) => number;

THIS WAY WE WILL GET NOTIFIED OF AN ERROR IF WE ASSIGN A FUNCTION WITH DIFFERENT PARAMETERS OR RETURN VALUES. IF WE ASSIGN TO sommaVar A FUNCTION WITH A RETURN OF VOID FOR EXAMPLE, WE WILL GET:

Type '(a: number, b?: number) => void' is not assignable to type '(a: number, b: any, number: any) => number'.
Type 'void' is not assignable to type 'number'.

COMPILE TS FILES WITH TYPESCRIPT COMPILER

tsc app.ts

This will create the app.js file:

// app.js

// code

function somma(a, b) {
    return a + b;
}
;
console.log(somma(3, 5));

// ecc

WE CAN USE

tsc app.ts --watch

OR

tsc app.ts -w

TO AUTOMATICALLY WATCH OUR .ts FILE FOR CHANGES AND UPDATE OUR .js FILE

[15:15:17] Starting compilation in watch mode...

[15:15:17] Found 0 errors. Watching for file changes.

IF WE USE

tsc --init

TS WILL CREATE A .json FILE AND THE COMPILER WILL AUTOMATICALLY MANAGE AND COMPILE .ts FILES

Created a new tsconfig.json with:                                                                     
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig

NOW AFTER USING --init WE CAN WATCH ALL FILES

tsc -w

WE CAN ADD AT THE END OF tsconfig.json AN ARRAY OF FILES/DIRECTORIES TO EXLUDE FROM OUR WATCH:

// [.json file]

    },

    "exclude": [
        "node_modules",
        "./file-to-exclude.ts"
    ]

}

WE SHOULD DECLARE TO EXCLUDE node_modules IF WE USE "exclude" IN tsconfig.json.

IF WE WANT TO CHECK OUR SOURCE TYPESCRRIPT FILES WITH THE BROWSER DEVTOOLS WE HAVE TO ENABLE "sourceMap" ON tsconfig.json

/* Create source map files for emitted JavaScript files. */
"sourceMap": true,

WE CAN SPECIFY A FOLDER CONTAINING OUR TS SOURCE FILES IN tsconfig.json:

/* Specify the root folder within your source files. */
"rootDir": "./src",

AND A FOLDER FOR OUR COMPILED JS FILES

/* Specify an output folder for all emitted files. */
"outDir": "./dist",

REMEMBER TO UPDATE THE SCRIPT PATH:

<script src="./dist/app.js" type="text/javascript"></script>

CLASSES

CLASSES WORK ALMOST THE SAME

class Persona {
    private nome: string;
    private cognome: string;
    constructor(nome: string, cognome: string) {
        this.nome = nome;
        this.cognome = cognome
    };

    getFullName(): string {
        return `${this.nome} ${this.cognome}`
    };

    greet(): void {
        console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
    };

    welcome(persona: Persona): string {
        return `Benvenuto ${persona.nome} ${persona.cognome}!`
    };

    changeSurname(surname: string) {
        this.cognome = surname;
    };

};

let mario: Persona;

mario = new Persona("Mario", "Rossi");

const luca = new Persona("Luca", "Gialli");

// full name: Luca Gialli
console.log('full name:', luca.getFullName());

// Ciao, sono Mario Rossi
mario.greet();

// Benvenuto Mario Rossi!
console.log(
    luca.welcome(mario)
);

CLASS PROPERTIES

PRIVATE PROPERTIES

WE CAN DECLARE A PROPERTY AS PRIVATE SO WE CAN'T REASSIGN IT WITHOUT A METHOD

class Persona {
    private nome: string;
    private cognome: string;

    // ...

    changeSurname(surname: string) {
        this.cognome = surname;
    };

};

TS CAN AUTOMATICALLY DETECT CONSTRUCTOR PROPERTIES WHEN DECLARED

class Persona {

    constructor(private nome: string, private cognome: string) {}

    getFullName(): string {
        return `${this.nome} ${this.cognome}`
    };

    greet(): void {
        console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
    };

    welcome(persona: Persona): string {
        return `Benvenuto ${persona.nome} ${persona.cognome}!`
    };

};

(WE CAN ADD SOME ERROR MANAGEMENT INSIDE THE MUSTACHE BRACKETS CONTRUCTOR)

class Persona {

    constructor(private nome: string, private cognome: string) {
        if (!nome || !cognome) {
            throw new Error("Nome e cognome devono essere forniti.");
        }
    };

    getFullName(): string {
        return `${this.nome} ${this.cognome}`
    };

    greet(): void {
        console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
    };

    welcome(persona: Persona): string {
        return `Benvenuto ${persona.nome} ${persona.cognome}!`
    };

};

READ ONLY PROPERTIES

WE CAN DECLARE A PROPERTY AS READ ONLY SO IT WE CAN ONLY READ BUT NOT CHANGE IT:

class Persona {

    constructor(private readonly nome: string, private cognome: string) {
    };

    // ...

};

STATIC PROPERTIES

class Persona {

    static nazione: string = "Italia";

    constructor(private nome: string, private cognome: string) {
    };

    // GETTER
    getFullName(): string {
        return `${this.nome} ${this.cognome}`
    };

    greet(): void {
        console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
    };

    welcome(persona: Persona): string {
        return `Benvenuto ${persona.nome} ${persona.cognome}!`
    };

    static getNazione(): string {
        return Persona.nazione;
    };

};

STATIC PROPERTIES CAN BE ACCESSED FROM THE CLASS ITSELF OR USING A GETTER METHOD:

console.log("Nazione di persona:", Persona.nazione);
// Nazione di persona: Italia

let mario: Persona

mario = new Persona("Mario", "Rossi");

console.log("Nazione di Mario:", mario.getNation());
// Nazione di Mario: Italia

CHILD CLASSES

PRIVATE PROPERTIES CAN BE ACCESSED ONLY ON THE CLASS IN WICH THEY ARE DECLARED, SO IF WE WANT TO ACCESS THE PARENT PROPERTIES nome AND cognome WE SHOULD SET THEM TO PROTECTED. A PROTECTED PROPERTY CAN BE ACCESSED BY THE CLASS AND HER CHILD CLASSES BUT NOT EDITED WITHOUT USING A SETTER METHOD.

class Persona {

    constructor(protected readonly nome: string, protected cognome: string) { };

    // ...

};

WE DECLARE nome AND cognome AS SUPER PROPERTIES TO ACCESS THE PARENT CONSTRUCTOR

class Studente extends Persona {
    constructor(
        nome: string,
        cognome: string,
        private matricola: number
    ) {
        super(nome, cognome);
    };

    getStudentData(): string {
        return `Nome: ${this.nome}, Cognome: ${this.cognome} - Matr: ${this.matricola}`;
    };

    // SETTER
    changeEnrCode(code: number) {
        this.matricola = code;
    };

};

let marco = new Studente('Marco', 'Verdi', 0o1234);
// Nome: Marco, Cognome: Verdi - Matr: 668

ABSTRACT CLASSES

WE CAN DECLARE AN ABSTRACT CLASS TO USE AS TEMPLATE. WE COULD USE Persona AS ABSTRACT CLASS AS IN OUR APP EVERY PERSON WILL HAVE A ROLE AND WE WILL NOT HAVE THE NEED TO GENERATE A Persona INSTANCE

abstract class Persona {

    constructor(protected readonly nome: string, protected cognome: string) { };

    // ...

};

THIS WAY WE CAN NOT CREATE A NEW Persona

// ๐Ÿ‘‡ Cannot create an instance of an abstract class. ๐Ÿ‘‡
const luca = new Persona("Luca", "Gialli"); // โŒ

BUT WE CAN CREATE A NEW Studente (WICH EXTENDS Persona):
// โœ”๏ธ
const marco = new Studente('Marco', 'Verdi', 0o1234);

WE CAN ALSO DECLARE ABSTRACT METHODS IN ABSTRACT CLASSES. WHEN DECLARED AN ABSTRACT METHOD DOES NOT NEED LOGIC, BUT ONLY DECLARATION:

abstract class Persona {

    constructor(protected readonly nome: string, protected cognome: string) { };

    // ...

    abstract greet(): void;

};

BUT EACH CHILD CLASSES NEED THE LOGIC OF THE DECLARED METHOD, WICH CAN CHANGE FROM CLASS TO CLASS:

class Studente extends Persona {

    constructor(
        nome: string,
        cognome: string,
        private matricola: number
    ) {
        super(nome, cognome);
    };

    // ...

    // ๐Ÿ‘‡
    greet(): void {
        console.log(`Ciao, sono ${this.nome} ${this.cognome}`);
    };

};

IF AN ABSTRACT CLASS HAS AN ABSTRACT METHOD EACH CHILD MUST HAVE IT'S LOGIC.

SINGLETON

SINGLETON ARE USED TO GET AN UNIQUE HARDCODED INSTANCE OF A CLASS.

class Preside {
    private static instance: Preside;

    constructor(private nome: string, private cognome: string) { };

    static getInstance() {
        // IF THE INSTANCE IS ALREADY DEFINED, RETURN IT
        if (Preside.instance) {
            return this.instance;
        }
        // IF NOT, CREATE A NEW INSTANCE WITH HARDCODED DATA
        this.instance = new Preside('Giovanni', 'Blu');
        return this.instance;
    };

    greet() {
        console.log(`Sono il Preside ${this.nome}, ${this.cognome}`);
    };

};

Preside.getInstance().greet();

INTERFACES

INTERFACES ARE USED TO DEFINE THE ABSTRACTION OF BEHAVIOURS OF METHODS OR PROPERTIES A CLASS CAN IMPLEMENT, WITHOUT HAVING TO EXTEND ANOTHER CLASS. EXAMPLE: A SMARTPHONE IS A DEVICE WICH HAS INTERNET CONNECTION, A TELPHONE IS A DEVICE WITHOUT INTERNET CONNECTION.

abstract class Device {

    constructor(protected nome: string, protected year: number) {};
    
    abstract turnOn(): void;
    abstract turnOff(): void;

    getYear() {
        console.log(`Questo ${this.nome} รจ stato prodotto nel ${this.year}`);
        
    }
};

interface internetConnection {
    ip: string;
    connect(): void;
    disconnect(): void;
};

class Smarphone extends Device implements internetConnection {

    ip: string;

    constructor(nome: string, year: number, ip: string) {
        super(nome, year);
        this.ip = ip;
    }

    connect(): void {
        console.log(`${this.nome} si รจ connesso all'indirizzo IP ${this.ip}`);
    }

    disconnect(): void {
        console.log(`${this.nome} si รจ disconnesso dall'indirizzo IP ${this.ip}`);
    }

    turnOn(): void {
        console.log(`${this.nome} si accende`);
    }

    turnOff(): void {
        console.log(`${this.nome} si spegne`);
    }

}

let iphone = new Smarphone('iPhone', 2019, '192.158.1.38');

// Questo iPhone รจ stato prodotto nel 2019
iphone.getYear();

// iPhone si accende
iphone.turnOn();

// iPhone si รจ connesso all'indirizzo IP 192.158.1.38
iphone.connect();

// iPhone si รจ disconnesso dall'indirizzo IP 192.158.1.38
iphone.disconnect();

// iPhone si spegne
iphone.turnOff();

AN INTERFACE CAN EXTEND ONE OR MORE INTERFACES:

interface connectionType extends internetConnection {
    connectType: string;
};

class Computer extends Device implements connectionType {

    connectType: string;
    ip: string;

    constructor(nome: string, year: number, ip: string, connectionType: string) {
        super(nome, year);
        this.connectType = connectionType;
        this.ip = ip;
    }

    connect(): void {
        console.log(`${this.nome} si รจ connesso all'indirizzo IP ${this.ip}`);
    }

    disconnect(): void {
        console.log(`${this.nome} si รจ disconnesso dall'indirizzo IP ${this.ip}`);
    }

    turnOn(): void {
        console.log(`${this.nome} si accende`);
    }

    turnOff(): void {
        console.log(`${this.nome} si spegne`);
    }

};

let macBook = new Computer('Macbook', 2017, '192.158.1.38', 'WiFi');

// WiFi
console.log(macBook.connectType);

GENERICS

GENERICS ARE TYPES THAT CAN BE USED TO

  • DEFINE TYPES FOR FUNCTION PARAMETERS
  • DEFINE TYPES FOR FUNCTION RETURN

WE CAN DECLARE THE DESIRED TYPE OF VALUES

const arr: Array<string> = ['string1', 'string2'];

T IS A BUILT-IN TYPE THAT DEFINES A GENERIC TYPE THIS WAY IT WILL GENERATE A GENERIC ARRAY OF THE SAME TYPE

function newArray<T>(items: T[]) {
    return new Array().concat(items);
};

THIS WAY IT WILL TAKE VIA INFERENCE NUMBER AS TYPE

// function newArray<number>(items: number[]): any[]
const arr1 = newArray([1,2,3]);

THIS WAY IT WILL TAKE VIA INFERENCE STRING AS TYPE

// function newArray<string>(items: string[]): any[]
const arr2 = newArray(['a','b','c']);

THIS WAY IT WILL TAKE VIA INFERENCE STRING OR NUMBER UNION AS TYPE

// function newArray<string | number>(items: (string | number)[]): any[]
const arr3 = newArray(['a','b',3]);

THIS WAY THIS WAY IT WILL ACCEPT ONLY NUMBERS AS PARAMETERS

const arr4 = newArray<number>([1,2,3]);

WE CAN ALSO DECLARE WE WILL PASS MULTIPLE TYPES AS UNION

const arr5 = newArray<number | string | boolean>([1,'a',true]);

WE CAN CONSTRAIN T TO BE A SPECIFIC TYPE

function newArray<T extends number>(items: T[]): T[] {
    return new Array().concat(items);
};
function newArray<T extends number | string>(items: T[]): T[] {
    return new Array().concat(items);
};

WE CAN DECLARE A CLASS THAT ACCEPT GENERIC VALUES THAT WILL BE TYPIZED BY INFERENCE

class List<T> {

    // IT HAS A GENERIC ARRAY HAS PRIVATE PROPERTY
    private list: T[] = [];

    addItem(item: T): void {
        this.list.push(item);
    };

    removeItem(item: T): void {
        this.list.splice(this.list.indexOf(item, 1));
    };
};

// AS WE CREATE A NEW ISTANCE, ALL TYPES WILL BE CHANGED BY INFERENCE
const list = new List<number>();

//๐Ÿ‘‡ IT WILL ACCEPT ONLY NUMBERS AS ARGUMENTS NOW ๐Ÿ‘‡
// (method) List<number>.addItem(item: number): void
list.addItem(1);

//๐Ÿ‘‡ Argument of type 'string' is not assignable to parameter of type 'number'. ๐Ÿ‘‡
list.addItem('a'); // โŒ

TO SUMMARIZE, HERE ASSIGNS TYPE number TO T:

const list = new List<number>();

DECORATORS

DECORATORS MUST BE ENABLED IN tsconfig.json

/* Enable experimental support for legacy experimental decorators. */
"experimentalDecorators": true,

A DECORATOR FACTORY IS A FUNCTION THAT RETURN A DECORATOR. WE DEFINE THE DESIDERED PARAMETERS IN OUR FUNCTION AND WE RETURN A DECORATOR THAT USES THE PARAMETERS AND THE CLASS PROPERTIES.

//๐Ÿ‘‡ FUNCTION THAT RETURNS A DECORATOR FACTORY ๐Ÿ‘‡
function createHtmlElement(template: string, id: string, text: string) {

    //๐Ÿ‘‡ FUNCTION THAT RETURNS A DECORATOR ๐Ÿ‘‡
    return function (constructor: any) {
    
        //๐Ÿ‘‡ CREATES THE HTML ELEMENT ๐Ÿ‘‡
        const container = document.getElementById(id);
        const element = document.createElement(template);

        //๐Ÿ‘‡ CREATES THE CONTENT USING THE CLASS CONSTRUCTOR ๐Ÿ‘‡
        const content = new constructor(text);

        if (container) {
            container.appendChild(element);
            element.textContent = content.text;
        }

    };

};

//๐Ÿ‘‡ USES THE DECORATOR FACTORY ๐Ÿ‘‡
@createHtmlElement('h1', 'container', 'DECORATORS TEST')
class DecoTextH1 {
    constructor(public text: string) { }
};

@createHtmlElement('p', 'container', 'DECORATORS TEST')
class DecoTextP {
    constructor(public text: string) { }
};

PROPERTY DECORATOR

WE CAN USE DECORATOR WHEN WE ACCESS A CLASS PROPERTY:

function propertyDecorator(target: any, property: string) {
    console.log("Property decorator executed. Property is:", property, " Target Class is:", target);
}

function parameterDecorator(target: any, methodName: string, parameterIndex: number) {
    console.log(`Parameter decorator executed. Target: ${target}, Method: ${methodName}, Parameter Index: ${parameterIndex}`);
}

class MyClass {
    @propertyDecorator
    myProperty: string;

    constructor(public myProperty: string) {
        this.myProperty = myProperty;
    }
}

// OUTPUT:

// Property decorator executed. Property is: myProperty  Target Class is:
// {
// constructor: class MyClass
// length: 1
// name: "MyClass"
// ECC
// }

WE CAN APPLY DECORATOR TO METHODS PARAMETERS OF A CLASS:

function parameterDecorator(target: any, methodName: string, parameterIndex: number) {
    console.log(`Parameter decorator executed. Target: ${target}, Method: ${methodName}, Parameter Index: ${parameterIndex}`);
}

class MyClass {
    @propertyDecorator    
    myProperty: string;


    constructor(myProperty: string) {
        this.myProperty = myProperty;
    }

    // 
    getProperty(@parameterDecorator property: string) {
        property = this.myProperty;
        console.log(`Property: ${property}`);
    }
}

IMPORT FILES

WE ADD TYPE MODULE ON THE SCRIPT TAG IN INDEX.HTML

<script src="./dist/app.js" type="module"></script>

WE MAKE SURE THAT IN tsconfig.json, UNDER compilerOptions THAT "module" AND target USE THE RIGHT VERSION OF ES

"target": "es2016",
"module": "ES6",

WE CREATE A SEPARATE FILE AND WE USE THE COMMAND export TO DECLARE THE DATA WE NEED

// utils.ts

export function logSuccess():void {
    console.log('successful import!');
};

export function logSum(a: number, b: number):void {
    console.log('Sum is ',a + b);
};

export function multiplication(a: number, b: number): number {
    return a * b;
};

WE IMPORT THE FUNCTIONS/DATA FROM THE FILE

// USE.js EXTENSION OR THE COMPILED FILE WILL NOT FIND THE FILE IMPORT
import { logSuccess, logSum, multiplication } from './utils.js';

logSuccess();

logSum(5, 2);

console.log(`Your operation result is ${multiplication(5, 2)}`);

WE CAN ALSO EXPORT AS AN OBJECT

function logSuccess():void {
    console.log('successful import!');
};

function logSum(a: number, b: number):void {
    console.log('Sum is ',a + b);
};

function multiplication(a: number, b: number): number {
    return a * b;
};

export { logSuccess, logSum, multiplication };

TS IN NODE.JS

https://www.typescripttutorial.net/typescript-tutorial/nodejs-typescript/

Create two sub-directories called build and src Youโ€™ll store the TypeScript code in the src directory. Once the TypeScript compiler compiles the source TypeScript files, it will store the output files in the build directory.

Create the tsconfig.json file:

tsc --init

When you compile the TypeScript files, the TypeScript compiler will use the options in the tsconfig.json to compile the project.

Now, you can open the tsconfig.json file. There are many options. In this tutorial, youโ€™ll focus on these two options:

rootdir โ€“ specifies the root directory of the TypeScript input files. outdir -stores the JavaScript output files.

These options are commented by default. And youโ€™ll need to uncomment ( remove the // at the beginning of the line) and change them as follows:

For the outDir option:

"outDir": "./build"

And for the rootDir option:

"rootDir": "./src"

INSTALL NODE.JS MODULES

The nodemon module allows you to automatically restart the application when you change the JavaScript source code.

The concurrently module runs multiple commands concurrently.

First, execute the npm init command from the root directory of the project:

npm init --yes

Next, install the nodemon and concurrently module:

npm install --g nodemon concurrently

Note that the -g flag will instruct npm to install these two modules globally. This allows you to use them in other projects.

Then, open the package.json file, and youโ€™ll something like this in the scripts option:

// ...  
"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
},
// ...

After that, change the scripts option to the following:

// ...
"scripts": {
    "start:build": "tsc -w",
    "start:run": "nodemon build/app.js",
    "start": "concurrently npm:start:*"
},
// ...

This "start:build": "tsc -w" will watch for changes in the ./src directory and compile them automatically.

This "start:run": "nodemon build/app.js" will automatically run the app.js in the ./build directory whenever the new file is generated.

This "start": "concurrently npm:start:"* runs all the commands that start with npm:start:*, which executes both start:build and start:run commands above.

Since the app.js will be the entry point for the Node.js program, you also need to change the following option in the package.json file to app.js:

From:

"main": "index.js"

To:

 "main": "app.js"

Finally, execute the following command:

npm start

To verify the configuration, you change some code in the app.ts. And youโ€™ll see the output in the console.