TypeScript Notes

Cheatsheet

Typescript Classes

Typescript Control Flow

Typescript Interfaces

Typescript Types

Typescript notes

  1. + will convert ‘string’ variable to ‘number’
  2. Install typescript: npm install typescript --save-dev as dev dependency
  3. Install typescript globally: npm install -g typescript

Compiling TS

  1. Initialize typescript - creates tsconfig.json typescript --init
  2. Run typescript: tsc

Useful VS extensions

  1. ESLint - To lint JS code
  2. Material Icon theme - Visual icons for files
  3. Path Intellisense - better support for imports
  4. Prettier - Formats code

Setup a fresh project with node live server

Install nodejs and npm

  1. npm init and add details - it’ll create package.json file
  2. run npm install --save-dev lite-server - installs lite-server and mark it as develepment only dependencies
  3. Add “start”: “lite-server” in package.json. It’ll give you a command npm start which will resolve to lite-server. So now npm start will start the project.
  4. If any changes in TS file, then need to run tsc

Core types

Javascript uses “dynamic types” (Resolved at runtime), TypeScript uses “static types” (set during development)

  1. number - All numbers integers or floats
  2. string - All texts - ‘’, “”, ``
  3. boolean - true/false
  4. objects - Of course object types can also be created for nested objects.

Let’s say you have this JavaScript object:

const product = {
  id: "abc1",
  price: 12.99,
  tags: ["great-offer", "hot-and-new"],
  details: {
    title: "Red Carpet",
    description: "A great carpet - almost brand-new!",
  },
};

This would be the type of such an object:

{
  id: string;
  price: number;
  tags: string[];
  details: {
    title: string;
    description: string;
  }
}

So you have an object type in an object type so to say. 5. Array - [1,2] 6. Tuple - [1,2] - Fixed length array

const person: {
  name: string,
  age: number,
  hobbies: string[],
  role: [number, string],
} = {
  name: "Chandan",
  age: 37,
  hobbies: ["books", "technology"],
  role: [2, "author"],
};
  1. enum - enum {NEW, OLD} - Enumerated global constant identifiers
enum Role {NORMAL, ADMIN, SUPERADMIN = 5}
enum MemberShipType {
    FREE = 10,
    GOLD = 'Gold',
    PLATINUM = 'Platinum'
}
  1. any - Any type is a valid any type

Type alias

type Combinable = string | number;
type ConversionDiscriptor = "as-number" | "as-string";

function combine(
  input1: Combinable,
  input2: Combinable,
  resultAs: ConversionDiscriptor
) {
  let result;
  if (
    (typeof input1 === "number" && typeof input2 === "number") ||
    resultAs === "as-number"
  ) {
    result = +input1 + +input2;
  } else {
    result = input1.toString() + input2.toString();
  }
  return result;
}

Another example:

function greet(user: { name: string, age: number }) {
  console.log("Hi, I am " + user.name);
}

function isOlder(user: { name: string, age: number }, checkAge: number) {
  return checkAge > user.age;
}
// To:

type User = { name: string, age: number };

function greet(user: User) {
  console.log("Hi, I am " + user.name);
}

function isOlder(user: User, checkAge: number) {
  return checkAge > user.age;
}

function return types

// Explicit return type. If not provided, TS will infer the return type
function add(n1: number, n2: number): number {
  return n1 + n2;
}

// return void
function printResult(num: number) {
  console.log("Result is: ", num);
}

printResult(add(5, 10));

Unknown type

let userInput: unknown;
let userName: string;

userInput = 5;
userInput = "Chandan";
// here explicit check of typeof userInput is required as
// userInput is of type `unknown`. if it was `any` then check wasn't required.
if (typeof userInput === "string") {
  userName = userInput;
}

never type

Mostly used for functions, which never returns anything, e.g. functions throwing error

function generateError(message: string, code: number): never {
  throw { message: message, errorCode: code };
}

generateError("Something went wrong", 400);

TypeScript Compiler configuration - Comprehensive doc here

watch mode - Keep tsc in watch mode while development

$> tsc -w
$> tsc --watch
$> tsc app.ts -w

exclude/include comiling files in tsconfig.js

Files to compile = included files - excluded files

{
  "compilerOptions": {
    // ...
  },
  "exclude": [
    "analytics.dev.ts",
    "**/*.dev.ts", // exclude all files in all folders with `.dev.ts` extension
    "node_modules" // NOTE: "node_modules" are excluded by default, but if `exclude` is added, then it must be specified.
  ],
  "include": [ // if include specified, then only files listed will be included in complier

  ]
}

Compiling ts into different version of ECMAScript

You can change the target to compile to older version of javascript/ECMAScript

// In tsconfig.json
{
  "compilerOptions": {
    "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
    //...
  }
}

lib - By default it contains all the libraries for interacting with “DOM” and other required libraries. Following set is by default available.

 "lib": [
      "DOM",
      "ES6",
      "DOM.Iterable",
      "ScriptHost"
    ],

sourceMap = true

This option generated .ts.map files, which gets rendered to browser’s source and can be used for debugging in .ts files instead of .js files.

sourceMap: true;

outDir: “./dist”

Specify a file that bundles all outputs into one JavaScript file. If ‘declaration’ is true, also designates a file that bundles all .d.ts output.

rootDir: “./src”

Specify the root folder within your source files. All other files outside ./src will be not be compiled by TS.

noEmitOnError: true/false

Disable emitting files if any type checking errors are reported.

“noUnusedLocals”: true

Enable error reporting when local variables aren’t read.

“noUnusedParameters”: true

Raise an error when a function parameter isn’t read.

“noImplicitReturns”: true,

Enable error reporting for codepaths that do not explicitly return in a function. if the function doesn’t have any return, then it’s fine, however, if the function returns anything in any branching of condition, then it must return in all branch.

JS Modules

ES feature support charte here

let vs var

key difference - let lets you define block scoped variable, which isn’t possible with var.

function signature

const printOutput = (output: string | number) => console.log(output);
// with function signature of types of argument and return - we don't need to specify it in the function implementation
const printOutput1: (a: number | string) => void = (output) =>
  console.log(output);

Spread operator

// Array spread
const hobbies = ["sports", "books"];
const activeHobbies = ["movies"];
const allHobbies = [...activeHobbies, ...hobbies];

activeHobbies.push(...hobbies);
// Object spread
const person = {
  name: "Chandan",
  age: 30,
};
// Following doesn't create a copy, instead it references to original person object.
const referenceCopiedPerson = person;
// Spread operator creates actual copy of person object
const copiedPerson = { ...person };

Using tuples for multi parameters, for same type of parameters with REST operator

// Accepts exactly three number parameters
const add = function (...numbers: [number, number, number]) {
  return numbers.reduce((total, num) => total + num);
};

Array/Object destructuring

// Array destructuring
const hobbies = ["biking", "reading", "programming", "startup"];
const [hobby1, hobby2, ...remaining] = hobbies;
// Object destructuring
const person = {
  firstName: "Chandan",
  age: 37,
};
// here left side variable name must match the key of person object.
// If you want to assign it to another named variable, use "objet-property"`:` "variable-name"
const { firstName: userName, age } = person;
console.log(userName, age);

Class and Interface

A class can be faked by an object, having exact same set of attributes and methods.

class typescript file

private and public

A method or property can be declared as private by putting private keyword in the beginning. You can define the property as public/private in the class constructor. You can also define a property as readonly

 constructor(private readonly id: string, public name: string){
        // this.id = id; // Do not need this, as these are explicity defined in constructor arguments.
        // this.name = name;
    }

prototype

The javascript prototype property allows you to add new properties to object constructor.

function Person(first, last, age, eyecolor) {
  this.firstName = first;
  this.lastName = last;
}

Person.prototype.nationality = "English";
Person.prototype.name = function () {
  return this.firstName + " " + this.lastName;
};

inhertiance and private/protected

super() must come before adding any new property to any subclass

class Department {
  private employees: string[];
  constructor(private id: string, public name: string){

  }
}
class ITDepartment extends Department {
    constructor(id: string, public admins: string[]){
        super(id, 'IT'); // super must come before adding any new property to this subclass
        this.admins = admins
    }

    addEmployee(name: string){
      // this.employees.push(name) <-> It'll throw error, as `employees` is `private` property of Department class
    }
}
const dept = new Department('dept_0', 'Electronics')
// dept.employees <-> Not accessible, as it's private property
const it = new ITDepartment('dep_1', ['Chandan'])
// it.employees.push('Darsh') <-> this will throw error, as `employees` is private property of `Department`

private properties are available only inside the class defining it. No instance of the class or child class can access these private properties directly. The defining class can refer to these private properties inside the class definition, however the child class can’t refer to these private properties even inside their class definition. If you want to access a property, which is not open to use outside class definition, however accessible from the class and subclass, use protected. This can be useful in overriding a function in sub-class if you want to use base class public/protected attributes.

getter and setters

Getter/Setter can be used to get/set any type of property(public, private, protected) and perform any logic needed. The methods defined with get and set can be directly accessed as property of object. No need to follow function call syntax.

Static methods and properties

static methods and properties are like class level property/variable and methods. To access a static property(inside non-static method definition of class or outside), you use className.static_property. To use static method, you use className.static_method(arguments). These static properties can’t be called on instance of class.

// example in existing libraries
Math.PI;
Math.pow(2,2);
// example
  class Department {
    static fiscalYear = 2023;
    constructor(private id: string, public name: string){
      console.log(Department.fiscalYear);
    }

    static createEmployee(name: string){
      return {employee: name}
    }
  }

Abstract class

Abstract class can be defined with abstract keyword. A method can be defined as abstract method with abstract keyword. A class can have an abstract method if and only if, the class itself is an abstract class.

abstract class Department {
  abstract describe(): void;
}

class ITDepartment extends Department {
  describe(){
    console.log("Defined abstract method in sub-class")
  }
}

Singleton class

Singleton instance will have exactly one instance of a singleton class. This can be done by defining static instance of type of class and a static method getInstance to get the singleton object and marking the constructor private

class AccountingDepartment{
  private static instance: AccountingDepartment;

  private constructor(private id: string, public reports: string[]){

  }

  static getInstance(){
    if(AccountingDepartment.instance){
      return this.instance;
    } else {
      this.instance = new AccountingDepartment('id1', [])
      return this.instance;
    }
  }
}

Interface

Interface describes a structure of object. It’s similar to custom type. However, alias type are mostly used with functions or define composite(union etc) types of base types (such as number, string etc). Interface is mostly used with objects.

interface Greetable {
    name: string;

    greet(text: string): void;
}

class Person implements Greetable {
    age = 30;

    constructor(public name: string){

    }
    greet(message: string){
        console.log(message)
    }
}

let user1: Greetable;

user1 = {
    name: "Chandan",
    greet(phrase: string) {
        console.log(phrase, this.name)
    }
}

user1.greet("Hi there, I'm")

interface doesn’t define a method, however, abstract class can have some method implementation.

A class can implement one or more interfaces, if so, then the class has to implement all the properties/methods of the interface.

interface can have readonly property definition, but it can’t have public or private. This readonly property can be assigned only at the time of creation of object. The behaviour of this readonly property can be changed in the class, which implements the interface.

An interface can extend another one or more interfaces. Multiple interfaces can be implemented in a class. A class can inherit from only one parent class, however, it can implment multiple interfaces.

inteface as function types

To define type, we use =, however, to define interface, we use {}. Using type for function types is probably is a better choice.

// type AddFunc = (a: number, b: number) => number;
interface AddFunc {
  (a: number, b: number): number;
}

let add: AddFunc;

add = function (num1: number, num2: number) {
  return num1 + num2;
};

A property or method can be marked as optional using ?.

interface AnotherInterface {
  optionalNickName?: string;
  optionalMethod?: (text: string) => void;
}

NOTE: interface is TypeScript feature to ease and structuring your code. There is no corresponding implementation in vanilla javascript.

Advanced types and Type Guards

Interface intersection

interface Admin {
  name: string;
  priviledges: string[];
}

interface Employee {
  name: string;
  startDate: Date;
}

interface ElevatedEmployee extends Admin, Employee {}

const e1: ElevatedEmployee = {
  name: "Chandan",
  priviledges: ["CEO", "CTO"],
  startDate: new Date(),
};

class GreetEmployee implements ElevatedEmployee {
  private static instance: GreetEmployee;
  constructor(
    public name: string,
    public priviledges: string[],
    public startDate: Date
  ) {}

  static getInstance() {
    if (GreetEmployee.instance) {
      return this.instance;
    } else {
      this.instance = new GreetEmployee("Chandan", ["CEO", "CTO"], new Date());
      return this.instance;
    }
  }

  static greetEmployee() {
    console.log("Welcome,", this.instance);
  }
}

const e2: GreetEmployee = GreetEmployee.getInstance();
GreetEmployee.greetEmployee();

Intersection of object-types/interfaces creates a combination type, which implements all the properties of all the composing types/interfaces.

type Car = {
  company: string,
  model: number,
};

type Bike = {
  company: string,
  mileage: number,
};

// Intersection type
type Vehicle = Car & Bike;

const myVehicle: Vehicle = {
  company: "Hyundai",
  model: 2022,
  mileage: 35,
};

// Union type
type UnknowVehicle = Car | Bike;

function vehicleInformation(vehicle: UnknowVehicle) {
  console.log(`Your vehicle company is ${vehicle.company}`);
  if ("mileage" in vehicle) {
    console.log(`Your vehicle mileage is ${vehicle.mileage}`);
  }
  if ("model" in vehicle) {
    console.log(`Your vehicle mileage is ${vehicle.model}`);
  }
}

Intersection of literal types, creates an intersection of the composing types.

type Combinable = number | string;
type Numeric = number | boolean;

// Universal can only be 'number'
type Universal = Combinable & Numeric;

// const uni: Universal = true; // won't work
const uni: Universal = 1;

Github commit here

With type or interface, we can have union | or intersection &.

Union of two object types, creates a type having either of the object’s type

To check the type of object inside function of composite type (i.e. Union |), you can use typeof for objects based on inheritance/types. For class based objects, you can use instanceof.

abstract class State {
  public capital: string;
  constructor(capital: string) {
    this.capital = capital;
  }
  abstract getCapital(): string;
}

class Bihar extends State {
  public language: string;

  constructor(capital: string, language: string) {
    super(capital);
    this.language = language;
  }

  getCapital() {
    return this.capital;
  }

  setLanguage(language: string) {
    this.language = language;
  }

  getLanguage(): string {
    return this.language;
  }
}

class Jharkhand extends State {
  public mines: string[];

  constructor(capital: string, mines: string[]) {
    super(capital);
    this.mines = mines;
  }
  getCapital(): string {
    return this.capital;
  }
}

type IndianStates = Bihar | Jharkhand;

const state1 = new Bihar("Patna", "Hindi");
const state2 = new Jharkhand("Ranchi", ["Coal Mines"]);

function indianStates(state: IndianStates) {
  console.log(state.getCapital());
  if (state instanceof Bihar) {
    console.log(state.language);
  }
  if (state instanceof Jharkhand) {
    console.log(state.mines);
  }
}

indianStates(state1);
indianStates(state2);

Discriminated Unions

In each object, keeping one common property, which describes the object. This property can be used to check the type of object to ensure type safety and to check which properties are available to object.

enum ANIMAL_TYPE {
  BIRDIE = "birdie",
  HORSIE = "horsie",
}
interface Bird {
  type: ANIMAL_TYPE.BIRDIE;
  flyingSpeed: number;
}

interface Horse {
  type: ANIMAL_TYPE.HORSIE;
  runningSpeed: number;
}

type Animal = Bird | Horse;

function movingSpeed(animal: Animal): void {
  let speed;
  switch (animal.type) {
    case ANIMAL_TYPE.BIRDIE:
      speed = animal.flyingSpeed;
      break;
    case ANIMAL_TYPE.HORSIE:
      speed = animal.runningSpeed;
      break;
  }
  console.log("Moving at speed ...", speed);
}

movingSpeed({ type: ANIMAL_TYPE.BIRDIE, flyingSpeed: 1000 });
movingSpeed({ type: ANIMAL_TYPE.HORSIE, runningSpeed: 100 });

When TS is unable to detect the actual type of an object, we can use Type Casting to let TS know the exact type of the object.

For example,

<p id="my-para"></p>
<input id="user-input" type="text" />

TS can’t define the precise type of the paragraph/userInput constant below. Thus we can do the typecasting.

const paragraph = document.querySelector("p")!;

const para = document.getElementById("my-para");

// One way of doing type casting. This type of syntax is also used in React
// const userInput = <HTMLInputElement>document.getElementById("user-input");

// Second way of Type casting
const userInput = document.getElementById("user-input") as HTMLInputElement;

userInput.value = "Please write a text here";

if (userInput) {
  (userInput as HTMLInputElement).value = "Please write your text here";
}

Index types

We might want to data-type/interface, which can have any number of properties. For example an ErrorContainer interface:

interface ErrorContainer {
  // {email: "Invalid email", userName: "Invalid username."}
  // id: number; // won't work, as we've defined [key: string]: string for all attributes
  //  [key: string]: string;
  [prop: string]: string;
}

const err1: ErrorContainer = {
  email: "Invalid email",
  // more attributes where key is string and value is also a string...
  userName: "No user found",
};

Function overloading

A function can be overloaded to define precise type of parameters and return values. Once function is overloaded, the return value can have behaviour of overloaded variable type if defined.

// function overloading
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
  if (typeof a === "string" || typeof b === "string") {
    return a.toString() + b.toString();
  }
  return a + b;
}

// This add can call methods defined for strings
const sumResult = add("Chandan", "Jhunjhunwal");
sumResult.split(" ");

// This add can call methods defined for numbers
const sumResultNum = add(1, 7);
if (typeof sumResultNum === "number") {
  console.log("I'm always true");
}

Optional Chaining

In case, we’re not sure of the property of the data (e.g. some data is retrieved from outside service), then we can use optional chaining to access the attributes of an object without any error (if it exists).

const fetchedData = {
  name: "Chandan",
  age: 37,
  // job: {
  //   title: "CEO",
  //   company: "Own company!",
  // },
};
// How we would do it, to ensure there is no error
console.log(fetchedData.job && fetchedData.job.title);
// or we can use `Optional chaining`
console.log(fetchedData?.job?.title);

Null coaleasing

To check assin a default only if a data is null. If the data is not null, keep it as it is.

// Nullish coalesing
const userInputNull = null;
const userInputBlank = "";
// Prints DEFAULT
console.log(userInputNull ?? "DEFAULT");
// Prints ""
console.log(userInputBlank ?? "DEFAULT");

Generics - Handbook

Generics is used to describe the complete type of object such as Array or promises or any complex combination of object using custom types

const array: Array<string | number> = ["Chandan", 1231];

const first = { name: "Chandan" };
const age = { age: 37 };

function vanillaMergeObjects(obj1: Object, obj2: Object) {
  return Object.assign(obj1, obj2);
}

const vanillaMergedObject = vanillaMergeObjects(first, age);
console.log(vanillaMergedObject);
// Even though the objects are merged, we can't access the property of objects directly
// console.log(vanillaMergedObject.name);
// console.log(vanillaMergedObject.age);

// The above problem is solved with Generics.
function mergeObject<T extends {}, U>(obj1: T, obj2: U) {
  return Object.assign(obj1, obj2);
}

const mergedObj = mergeObject(first, age);
console.log(mergedObj.name);
console.log(mergedObj.age);

// we could also do like, but it's redundant.
const mergedObj1 = mergeObject<{ name: string }, { age: number }>(first, age);

A type check constraint can also be added in the generic type definition

// To ensure the correct objects are passed, instead of any in generic type T and U
function mergeObjectWithConstraint<T extends object, U extends object>(
  obj1: T,
  obj2: U
) {
  return Object.assign(obj1, obj2);
}

// following will throw error due to type check for generic types T and U
// const mergedConstrainedObj = mergeObjectWithConstraint({ name: "Chandan" }, 30);
const mergedConstrainedObj = mergeObjectWithConstraint(
  { name: "Chandan" },
  { age: 30 }
);

We can also write function with generic types, which implements a/more particular method.

// We might want to write a function, which takes any object as argument,
// which responds to `length` method.

interface Lengthy {
  length: number;
}

function printAndDescribe<T extends Lengthy>(element: T): [T, string] {
  let descriptionText = "Got no value";
  if (element.length === 1) {
    descriptionText = "Got 1 value.";
  } else if (element.length > 1) {
    descriptionText = "Got " + element.length + " values.";
  }
  return [element, descriptionText];
}

console.log(printAndDescribe("Hello"));
console.log(printAndDescribe(""));
console.log(printAndDescribe([]));
console.log(printAndDescribe(["Chandan"]));
console.log(printAndDescribe(["Chandan", "Jhunjhunwal"]));

NOTE Usually if one parameter of a function is of generic type, then all the parameters are of generic type

To check the keyof a generic type object, use keyof

// Check the keyof a generic type
function extractAndConvert<T extends object, U extends keyof T>(
  obj: T,
  key: U
) {
  return "Value is: " + obj[key];
}

// console.log(extractAndConvert({}, 'name')) // throws error, no key 'name' in {}
// console.log(extractAndConvert({ name: "Chandan" }, "age")); // throws error, as no key 'age' in {name: "Chandan"}
console.log(extractAndConvert({ name: "Chandan" }, "name"));

Generic classes - Can be used to define a generic type of class

class DataStorage<T extends number | string | boolean> {
  private data: T[] = [];

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

  removeItem(item: T) {
    if (this.data.indexOf(item) === -1) {
      return;
    }
    this.data.splice(this.data.indexOf(item), 1);
  }

  listItems() {
    return [...this.data];
  }
}

const stringStorage = new DataStorage<string>();

stringStorage.addItem("Chandan");
stringStorage.addItem("Jhunjhunwal");
stringStorage.addItem("test");
stringStorage.removeItem("test");
console.log(stringStorage.listItems());

Partial

Partial allows us to define type for an object even if a part of property is defined for the object.

interface Human {
  name: string;
  age: number;
  weight: number;
}
// If person could be Partial of interface Human, Partial<T> can be used.
function partialDefinition<T extends Human>(person: Partial<T>) {
  // let person: Partial<T> = {}
  return person;
}

console.log(partialDefinition({ name: "Chandan" }));

function returnPartialObj(name: string, age: number, weight: number): Human {
  let myHuman: Partial<Human> = {};

  myHuman.name = name;
  myHuman.age = age;
  myHuman.weight = weight;
  //   myHuman.whatNot = 12;
  return myHuman as Human;
}

console.log(returnPartialObj("Chandan", 21, 80));

Readonly generics type

// Readonly objects
const iAmReadOnly: Readonly<string[]> = ["Chandan", "Jhunjhunwal"];
// iAmReadOnly.push("Test");
console.log(iAmReadOnly);

Decorator

Decorator is experimental feature of TypeScript. To enable decorator, update tsconfig.json by uncommeting "experimentalDecorators": true

In the code below, the decorator Logger accepts constructor of the class Person. The decorator gets executed, when the class Person is defined in javascript file. It’s not executed, when the object of Person is instantiated.

function Logger(constructor: Function) {
  console.log("Logging...");
  console.log(constructor); // it'll log the class
}

@Logger
class Person {
  name = "Chandan";
  constructor() {
    console.log("Creating a new person...");
  }
}

A decorator factory can be used to customise the decorator, by returning a function, which takes constructor as an argument.

// Decorator factory
function Logger(logString: string) {
  return function (constructor: Function) {
    console.log(logString);
    console.log(constructor);
  };
}

@Logger("LOGGING-PERSON")
class Person {
  name = "Chandan";
  constructor() {
    console.log("Creating person....");
  }
}

Decorator can also be used for templating.

In case a class has more than one decorators, then Decorators are invoked/executed from nearest decorator to farther decorator defined on the class i.e. Bottom-UP fashion. However, the actual declaration of the decorators happen in the same order as it’s defined on the file.

@WithTemplate("<h1>My Person - Chandan</h1>", "app") // invoked after @Logger
@Logger("LOGGING-PERSON") // first invoked
class Person {
  name = "Chandan";
  constructor() {
    console.log("Creating person....");
  }
}

Default Decorator Arguments - to be passed to decorator’s returned function or to be defined as signature argument of decorator

Class - constructor - These decorator can return override constructor, ie. modifying the behaviour of class (instance/accessor)Methods - object prototype + name of method + method descriptor - These decorator can change method/accessor, ie. modifying the behaviour of method/accessor static methods - object constructor + name of method + method descriptor property - object prototype + name of property - These decorator’s returned value is ingored argument decorator - object prototype + name of method having the argument + position - These decorator’s returned value is ingored

If your decorator takes the above arguments in their signature, the decorator can be called without (), however, if you want to return a decorated function and have these arguments available to them, then you need to decorate with ()

function MethodDecorator() {
  console.log("Decorating method...");
  return function (
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
  ) {
    console.log("//MethodDecorator - START");
    console.log(target);
    console.log(propertyKey);
    console.log(descriptor);
    console.log("//MethodDecorator - END");
  };
}

function AnotherMethodDecorator(
  target: any,
  methodName: string,
  descriptor: PropertyDescriptor
) {
  console.log("//AnotherMethodDecorator - Without a return function - START");
  console.log(target);
  console.log(methodName);
  console.log(descriptor);
  console.log("//AnotherMethodDecorator - Without a return function - END");
}


@AnotherMethodDecorator // notice - NO `()` here
  getAge() {
    return this._age;
  }

  @MethodDecorator() // notice - required `()` here
  draw() {
    console.log("Drawing....");
  }


Decorator executed at the time of defining the class. They don’t execute when a new instance of a the class is defined. Decorators meant for changing the behaviour of class/method/attributes at the time of definition to change their behaviour or do some behind the scene work or store some metadata etc. Decorator add some extra feature, which is tagged with the class/method/attributes declaration. However, decorator can also be written to do something at the run-time (i.e. when object is instantiated etc).

Autobind

Many times, when you want to call a function from another function, the definition of this can change. For example:

class Person {
  name = "Chandan";
  showMessage() {
    console.log(this.name);
  }
}
const p = new Person();
const button = document.querySelector('button')!;
// button.addEventListener('click', p.showMessage) // won't work, as `this` will point to the `target` of `addEventListener`
button.addEventListener('click', p.showMessage.bind(p))

This can be achieved with decorator. Define a get method in a modified method descriptor, which will get executed before the actual method execution.

function Autobind() {
  return function (
    _target: any,
    _methodName: string,
    descriptor: PropertyDescriptor
  ) {
    const originalMethod = descriptor.value;
    const modifiedDiscriptor: PropertyDescriptor = {
      configurable: true,
      enumerable: false,
      get() {
        // it's like executing extra logic before the `value` is returned
        const bindFn = originalMethod.bind(this);
        return bindFn;
      },
    };
    return modifiedDiscriptor;
  };
}
class Legend {
  name = "Chandan";

  @Autobind()
  showMessage() {
    console.log(this.name);
  }
}
const p = new Legend();
const button = document.querySelector("button")!;
// button.addEventListener("click", p.showMessage); // won't work, as `this` will point to the `target` of `addEventListener`
// button.addEventListener("click", p.showMessage.bind(p));

// Will start working with Autobind decorator, as `this` now correctly points to Legend object
button.addEventListener("click", p.showMessage);

Decorator can also be used to validate property of class, such as required, positive etc.

Class validator library - here

Typescript debugging in VS Code

https://code.visualstudio.com/docs/typescript/typescript-debugging

Gotchas | Tips & Tricks

  1. When adding js file reference in <head>, ensure you add defer to defer the load of script post document load.
<head>
  <script src="app.js" defer></script>
</head>
  1. To get the class name of a given object, use obj.__proto__.constructor.name NOTE: Every javascript function has name key.
class Course {
  title: string;
  price: number;

  constructor(t: string, p: number){
    this.title = t;
    this.price = p;
  }
}

// Tested on browser console
$> const course = new Course('title', 10)
$> course.__proto__.constructor
$> course.__proto__.constructor.name
'Course'
$> course.__proto__

  1. A) const node = document.importNode(this.projectTemplate.content,true); creates a few child and if you want to access html child, you’ll have to use node.firstElementChild and NOT node.firstChild ``B) htmltemplatedoesn’t appear on page. It acts as a hidden from UI element. The template can be used to createHTMLFragments, which can appended to any document’s node. user importNode to create imported node of the template.

  2. Three ways of binding this:

<button id="btn1">Test1</button>
<button id="btn2">Test2</button>
<button id="btn3">Test3</button>
<button id="btn4">Test4</button>

<script>

  class Test1 {
    constructor() {
      btn1.addEventListener('click', this.logThis);
    }
    logThis() {
      console.log(this);      // button (since we didn't bind this)
    }
  }

  class Test2 {
    constructor() {
      btn2.addEventListener('click', this.logThis.bind(this));
    }
    logThis() {
      console.log(this);      // class instance
    }
  }

  class Test3 {
    constructor() {
      btn3.addEventListener('click', () => this.logThis());
    }
    logThis() {
      console.log(this);      // class instance
    }
  }

  class Test4 {
    constructor() {
      btn4.addEventListener('click', this.logThis);
    }
    logThis = () => {
      console.log(this);      // class instance
    }
  }

  const test1 = new Test1();
  const test2 = new Test2();
  const test3 = new Test3();
  const test4 = new Test4();

</script>

TypeScript interative Shell

ts-node can be used for TypeScript execution and REPL for Node.js

npm install -g ts-node

Then launch the REPL by ts-node

Copy of an array

const ar = [1, 2, 3];
const ar2 = [...ar];
const ar3 = ar.slice();

To clean up existing built files.

$>  npm install --save-dev clean-webpack-plugin

TS Script useful utilities:

  1. https://github.com/typestack/class-transformer
  2. https://github.com/typestack/class-validator
  3. JavaScript to TypeScript types - https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types
  4. If a javascript library doesn’t have TS type definition, then you can use declare as last resort.
declare var GLOBAL: any;

TypeScript Promise

A promise need a Type for resolve function. i.e. on success, it’ll return an object of type Type. In case of failure (reject), since we don’t know, what will be returned, the reject’s return type is by default any.

const myPromise =
  new Promise() <
  Type >
  function (resolve, reject) {
    // logic
  };
// Alternatively
let myAnotherPromise: Promise<Type> = new Promise(function (
  resolve,
  reject
) {});

The promise function Object() accepts an executor callback, which the compiler will call with the following two arguments:

  1. resolve — This is a function for resolving the promise.
  2. reject — This is a function for rejecting the promise.

As a result, our commitment can either be fulfilled or denied. The resolve part is handled by .then, and the reject part is handled by .catch.

function types can be defined as:

const fetchHtmlPage: () => Promise<string> = () => {
  // Function body
};
// OR alternatively
const fetchHtmlPage = (): => Promise<string> = () => {
  // Function body
};

await operator is used to wait for a Promise. It can only be used inside an async function within regular JavaScript code; however it can be used on its own with JavaScript modules.

TypeScript + React

  1. Install create-react-app package
$> npm install -g create-react-app
  1. Now create an app with typescript
$> create-react-app <your-app-name> --template typescript

Read input field value

Using useRef : a mutable reference object.

  1. First import useRef
  2. Define a const/variable textInputRef using useRef. In useRef also define the Type of HTML Element(which you want to reference to) and initial value
  3. Define ref on the input element with a variable/const defined earlier
  4. Now the value of user input can be read by textInputRef.current.value
import React, { FormEvent, useRef } from "react";

const NewToDo: React.FC = () => {
    const textInputRef = useRef<HTMLInputElement>(null)

    function submitEventHandler(event: FormEvent) {
        event.preventDefault()
        const inputText = textInputRef.current!.value
        console.log(inputText)
    }
    return <form onSubmit={submitEventHandler}>
        <label htmlFor="new-todo">Todo Title</label>
        <input type="text" id="new-todo" ref={textInputRef}/>
        <button type="submit">Add To Do</button>
    </form>
}

export default NewToDo

Defining props on a component

Let’s say we’ve a function myPersonalEventHandler, which we want to pass to a component <MyPersonalComponent /> as props

  <MyPersonalComponent onPersonalAction={myPersonalEventHandler}>

This onPersonalAction must be defined in props definition of MyPersonalComponent component

inteface PersonalProps {
  onPersonalAction: (text: string) => void;
}
const MyPersonalComponent: React.FC<PersonalProps> = (props) => {
  props.onPersonalAction("It'll call function myPersonalEventHandler")
}

Notice, the onPersonalAction is a prop, defined on the component, however the value inside {}, i.e. myPersonalEventHandler is available inside the original calling component. Here function myPersonalEventHandler’s reference is passed to the component via onPersonalAction prop.

Use state to manage inputs

import React, {useState} from 'react';
import TodoList from './components/ToDoList';
import NewToDo from './components/NewToDo';
import { ToDo } from './models/todo.model';

function App() {
  const [todos, setToDo] = useState<ToDo[]>([])

  const onNewToDo = (text: string) => {
    setToDo([...todos, {id: Math.trunc(Math.random()*1000), name: text}])
    console.log(todos)
  }
  return (
    <div className="App">
      <NewToDo onToDo={onNewToDo}/>
      <TodoList items={todos}/>
    </div>
  );
}

export default App;

React might not update the todo immediately, so using ...todos might result in inconsistencies, so instead using the function signature of setState.

const onNewToDo = (text: string) => {
  setToDo((prevToDos) => [
    ...prevToDos,
    { id: Math.trunc(Math.random() * 1000), name: text },
  ]);
};

Delete a todo

You can add any number of props along with the components, which are defined on the component.

In express, you get vanilla server. You can add middleware to get more functionalities. For example

  1. error middleware here
  2. morgan middleware for logging
  3. Router middleware for handling CRUD requests with URL /todos
  4. json from body-parser to parse the body of request to JSON ```javascript import express, { Express, Request, Response, NextFunction } from “express”; import morgan from “morgan”; import { json } from “body-parser”; import { ToDoRouter } from “./routes/todos.routes”;

const app: Express = express(); const port: number = 3000;

// Middlewares app.use(morgan(“combined”)); app.use(json()); app.use(“/todos”, ToDoRouter); // Error Handling middleware app.use((err: Error, req: Request, res: Response, next: NextFunction) => { res.status(500).json({ message: err.message }); });

// app.request(err: Error, req: Request, res: Response, next: Next) app.listen(port);

```

This is a sapling 🌱 in my digital garden 🏡.

Notes mentioning this note


Here are all the notes in this garden, along with their links, visualized as a graph.