Baby steps in TypeScript
to seniority

What is TypeScript

TypeScript is a strong static typed compiled superset of JavaScript

Yeah sure..

TypeScript builds on top of JavaScript. First, you write the TypeScript code. Then, you compile the TypeScript code into plain JavaScript code using a TypeScript compiler.

What is TypeScript

TypeScript is a strong static typed compiled superset of JavaScript

What is TypeScript

function add(x: number, y: number): number {
  return x + y
}

TypeScript is a strong static typed compiled superset of JavaScript

What is TypeScript

function add(x: number, y: number): number {
  return x + y
}

add('2', '3')

TypeScript is a strong static typed compiled superset of JavaScript

What is TypeScript

TypeScript is a strong static typed compiled superset of JavaScript

function add(x: number, y: number): number {
  return x + y
}

add(2, 3)
index.ts
function add(x, y) {
  return x + y
}

add(2, 3)
index.js
ts-compiler

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

function add(x, y) {
  return x + y
}

function grossSalary(salary, bonus) {
  return salary + salary * bonus
}

console.log(add(2, '3')) // 23

console.log(grossSalary('2000', 0.3)) // 2000600 WTF!

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

function add(x: number, y: number) {
  return x + y
}

function grossSalary(salary: number, bonus: number) {
  return salary + salary * bonus
}

console.log(add(2, '3')) // TypeError

console.log(grossSalary('2000', 0.3)) // TypeError

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

function add(x: number, y: number): number {
  return x + y
}

for (let i of Array(1e4).keys()) {
  console.log(add(i, i + 1))
}
"use strict";
function add(x, y) {
    return x + y;
}
for (let i of Array(1e4).keys()) {
    console.log(add(i, i + 1));
}

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

"use strict";
function add(x, y) {
    return x + y;
}
for (let i of Array(1e4).keys()) {
    console.log(add(i, i + 1));
}
node --print-opt-code index.js > opt-code.txt

WHY TYPESCRIPT

More intentful code

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code


--- Optimized code ---
optimization_id = 19
source_position = 10348
kind = OPTIMIZED_FUNCTION
stack_slots = 8
compiler = turbofan
address = 0x247a506095c1

Instructions (size = 716)
0x247a50609620     0  488b59c0       REX.W movq rbx,[rcx-0x40]
0x247a50609624     4  f6430f01       testb [rbx+0xf],0x1
0x247a50609628     8  740d           jz 0x247a50609637  <+0x17>
0x247a5060962a     a  49ba80d93d0100000000 REX.W movq r10,0x13dd980  (CompileLazyDeoptimizedCode)
0x247a50609634    14  41ffe2         jmp r10
0x247a50609637    17  55             push rbp
0x247a50609638    18  4889e5         REX.W movq rbp,rsp
0x247a5060963b    1b  56             push rsi
0x247a5060963c    1c  57             push rdi
0x247a5060963d    1d  4883ec20       REX.W subq rsp,0x20
0x247a50609641    21  488975e0       REX.W movq [rbp-0x20],rsi
0x247a50609645    25  48897de8       REX.W movq [rbp-0x18],rdi
0x247a50609649    29  493b65e0       REX.W cmpq rsp,[r13-0x20] (external value (StackGuard::address_of_jslimit()))
0x247a5060964d    2d  0f86a1010000   jna 0x247a506097f4  <+0x1d4>
0x247a50609653    33  488b75e0       REX.W movq rsi,[rbp-0x20]
0x247a50609657    37  488b7e1f       REX.W movq rdi,[rsi+0x1f]
0x247a5060965b    3b  4c8b4617       REX.W movq r8,[rsi+0x17]
0x247a5060965f    3f  4d8b802f020000 REX.W movq r8,[r8+0x22f]
0x247a50609666    46  4d394528       REX.W cmpq [r13+0x28] (root (the_hole_value)),r8
0x247a5060966a    4a  0f84e1010000   jz 0x247a50609851  <+0x231>
0x247a50609670    50  40f6c701       testb rdi,0x1
0x247a50609674    54  0f8412020000   jz 0x247a5060988c  <+0x26c>
0x247a5060967a    5a  4c8b4fff       REX.W movq r9,[rdi-0x1]
0x247a5060967e    5e  4d394d78       REX.W cmpq [r13+0x78] (root (symbol_map)),r9
0x247a50609682    62  0f8510020000   jnz 0x247a50609898  <+0x278>
0x247a50609688    68  493bf8         REX.W cmpq rdi,r8
0x247a5060968b    6b  0f8410000000   jz 0x247a506096a1  <+0x81>
0x247a50609691    71  488b7e27       REX.W movq rdi,[rsi+0x27]
0x247a50609695    75  49c7c502000000 REX.W movq r13,0x2      ;; debug: deopt position, script offset '10629'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'Insufficient type feedback for generic named access'
                                                             ;; debug: deopt index 2
0x247a5060969c    7c  e8bf890b00     call 0x247a506c2060     ;; soft deoptimization bailout
0x247a506096a1    81  488b5627       REX.W movq rdx,[rsi+0x27]
0x247a506096a5    85  48b9e10b7a2aaa390000 REX.W movq rcx,0x39aa2a7a0be1    ;; object: 0x39aa2a7a0be1 <String[#7]: _stdout>
0x247a506096af    8f  b802000000     movl rax,0x2
0x247a506096b4    94  488bfe         REX.W movq rdi,rsi
0x247a506096b7    97  49ba0002410100000000 REX.W movq r10,0x1410200  (LoadICTrampoline)
0x247a506096c1    a1  41ffd2         call r10
0x247a506096c4    a4  488945d8       REX.W movq [rbp-0x28],rax
0x247a506096c8    a8  488b7d10       REX.W movq rdi,[rbp+0x10]
0x247a506096cc    ac  49397d30       REX.W cmpq [r13+0x30] (root (null_value)),rdi
0x247a506096d0    b0  0f8413010000   jz 0x247a506097e9  <+0x1c9>
0x247a506096d6    b6  4c8b45d8       REX.W movq r8,[rbp-0x28]
0x247a506096da    ba  41f6c001       testb r8,0x1
0x247a506096de    be  0f84cc010000   jz 0x247a506098b0  <+0x290>
0x247a506096e4    c4  49b9d92440251e020000 REX.W movq r9,0x21e254024d9    ;; object: 0x021e254024d9 <Map(HOLEY_ELEMENTS)>
0x247a506096ee    ce  4d3948ff       REX.W cmpq [r8-0x1],r9
0x247a506096f2    d2  0f85c4010000   jnz 0x247a506098bc  <+0x29c>
0x247a506096f8    d8  4d8b4817       REX.W movq r9,[r8+0x17]
0x247a506096fc    dc  4d8b89df000000 REX.W movq r9,[r9+0xdf]
0x247a50609703    e3  4d394d40       REX.W cmpq [r13+0x40] (root (false_value)),r9
0x247a50609707    e7  0f8438000000   jz 0x247a50609745  <+0x125>
0x247a5060970d    ed  4d394d48       REX.W cmpq [r13+0x48] (root (empty_string)),r9
0x247a50609711    f1  0f842e000000   jz 0x247a50609745  <+0x125>
0x247a50609717    f7  4d8b59ff       REX.W movq r11,[r9-0x1]
0x247a5060971b    fb  41f6430d10     testb [r11+0xd],0x10
0x247a50609720   100  0f851f000000   jnz 0x247a50609745  <+0x125>
0x247a50609726   106  4d399dc0000000 REX.W cmpq [r13+0xc0] (root (heap_number_map)),r11
0x247a5060972d   10d  0f8409010000   jz 0x247a5060983c  <+0x21c>
0x247a50609733   113  4d399d40010000 REX.W cmpq [r13+0x140] (root (bigint_map)),r11
0x247a5060973a   11a  0f84ed000000   jz 0x247a5060982d  <+0x20d>
0x247a50609740   120  e9a4000000     jmp 0x247a506097e9  <+0x1c9>
0x247a50609745   125  498b501f       REX.W movq rdx,[r8+0x1f]
0x247a50609749   129  49395520       REX.W cmpq [r13+0x20] (root (undefined_value)),rdx
0x247a5060974d   12d  0f847c000000   jz 0x247a506097cf  <+0x1af>
0x247a50609753   133  48b9f9af0c521a1e0000 REX.W movq rcx,0x1e1a520caff9    ;; object: 0x1e1a520caff9 <String[#5]: error>
0x247a5060975d   13d  b804000000     movl rax,0x4
0x247a50609762   142  48bb8197f66f783e0000 REX.W movq rbx,0x3e786ff69781    ;; object: 0x3e786ff69781 <FeedbackVector[6]>
0x247a5060976c   14c  48be4948cc952a280000 REX.W movq rsi,0x282a95cc4849    ;; object: 0x282a95cc4849 <FunctionContext[56]>
0x247a50609776   156  49ba8002410100000000 REX.W movq r10,0x1410280  (KeyedLoadIC)
0x247a50609780   160  41ffd2         call r10
0x247a50609783   163  a801           test al,0x1
0x247a50609785   165  0f8426000000   jz 0x247a506097b1  <+0x191>
0x247a5060978b   16b  488b78ff       REX.W movq rdi,[rax-0x1]
0x247a5060978f   16f  0fb67f0d       movzxbl rdi,[rdi+0xd]
0x247a50609793   173  83e712         andl rdi,0x12
0x247a50609796   176  83ff02         cmpl rdi,0x2
0x247a50609799   179  0f8512000000   jnz 0x247a506097b1  <+0x191>
0x247a5060979f   17f  b801000000     movl rax,0x1
0x247a506097a4   184  488b7d10       REX.W movq rdi,[rbp+0x10]
0x247a506097a8   188  4c8b45d8       REX.W movq r8,[rbp-0x28]
0x247a506097ac   18c  e923000000     jmp 0x247a506097d4  <+0x1b4>
0x247a506097b1   191  49394520       REX.W cmpq [r13+0x20] (root (undefined_value)),rax
0x247a506097b5   195  0f840c000000   jz 0x247a506097c7  <+0x1a7>
0x247a506097bb   19b  49c7c507000000 REX.W movq r13,0x7      ;; debug: deopt position, script offset '21107'
                                                             ;; debug: deopt position, inlining id '0'
                                                             ;; debug: deopt reason 'Insufficient type feedback for generic named access'
                                                             ;; debug: deopt index 7
0x247a506097c2   1a2  e899880b00     call 0x247a506c2060     ;; soft deoptimization bailout
0x247a506097c7   1a7  488b7d10       REX.W movq rdi,[rbp+0x10]
0x247a506097cb   1ab  4c8b45d8       REX.W movq r8,[rbp-0x28]
0x247a506097cf   1af  33c0           xorl rax,rax
0x247a506097d1   1b1  4c8bc8         REX.W movq r9,rax
0x247a506097d4   1b4  83f800         cmpl rax,0x0
0x247a506097d7   1b7  0f850c000000   jnz 0x247a506097e9  <+0x1c9>
0x247a506097dd   1bd  49c7c508000000 REX.W movq r13,0x8      ;; debug: deopt position, script offset '11128'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'Insufficient type feedback for generic named access'
                                                             ;; debug: deopt index 8
0x247a506097e4   1c4  e877880b00     call 0x247a506c2060     ;; soft deoptimization bailout
0x247a506097e9   1c9  498b4520       REX.W movq rax,[r13+0x20] (root (undefined_value))
0x247a506097ed   1cd  488be5         REX.W movq rsp,rbp
0x247a506097f0   1d0  5d             pop rbp
0x247a506097f1   1d1  c21000         ret 0x10
0x247a506097f4   1d4  48bf0000000088000000 REX.W movq rdi,0x8800000000
0x247a506097fe   1de  57             push rdi
0x247a506097ff   1df  b801000000     movl rax,0x1
0x247a50609804   1e4  48bba0cd090100000000 REX.W movq rbx,0x109cda0
0x247a5060980e   1ee  48be210160e9fc1a0000 REX.W movq rsi,0x1afce9600121    ;; object: 0x1afce9600121 <NativeContext[243]>
0x247a50609818   1f8  488bf8         REX.W movq rdi,rax
0x247a5060981b   1fb  49ba4063440100000000 REX.W movq r10,0x1446340  (CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit)
0x247a50609825   205  41ffd2         call r10
0x247a50609828   208  e926feffff     jmp 0x247a50609653  <+0x33>
0x247a5060982d   20d  41f74107feffff7f testl [r9+0x7],0x7ffffffe
0x247a50609835   215  75b2           jnz 0x247a506097e9  <+0x1c9>
0x247a50609837   217  e909ffffff     jmp 0x247a50609745  <+0x125>
0x247a5060983c   21c  c4c17b104107   vmovsd xmm0,[r9+0x7]
0x247a50609842   222  c5f157c9       vxorpd xmm1,xmm1,xmm1
0x247a50609846   226  c5f92ec8       vucomisd xmm1,xmm0
0x247a5060984a   22a  759d           jnz 0x247a506097e9  <+0x1c9>
0x247a5060984c   22c  e9f4feffff     jmp 0x247a50609745  <+0x125>
0x247a50609851   231  49bb31bb792aaa390000 REX.W movq r11,0x39aa2a79bb31    ;; object: 0x39aa2a79bb31 <String[#10]: kUseStdout>
0x247a5060985b   23b  4153           push r11
0x247a5060985d   23d  48897dd8       REX.W movq [rbp-0x28],rdi
0x247a50609861   241  4c8945d0       REX.W movq [rbp-0x30],r8
0x247a50609865   245  48bba0c3090100000000 REX.W movq rbx,0x109c3a0
0x247a5060986f   24f  b801000000     movl rax,0x1
0x247a50609874   254  4c8bde         REX.W movq r11,rsi
0x247a50609877   257  48be210160e9fc1a0000 REX.W movq rsi,0x1afce9600121    ;; object: 0x1afce9600121 <NativeContext[243]>
0x247a50609881   261  4c8b1595ffffff REX.W movq r10,[rip+0xffffff95]
0x247a50609888   268  41ffd2         call r10
0x247a5060988b   26b  90             nop
0x247a5060988c   26c  49c7c500000000 REX.W movq r13,0x0      ;; debug: deopt position, script offset '10578'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'Smi'
                                                             ;; debug: deopt index 0
0x247a50609893   273  e8c8870300     call 0x247a50642060     ;; eager deoptimization bailout
0x247a50609898   278  49c7c501000000 REX.W movq r13,0x1      ;; debug: deopt position, script offset '10578'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'not a Symbol'
                                                             ;; debug: deopt index 1
0x247a5060989f   27f  e8bc870300     call 0x247a50642060     ;; eager deoptimization bailout
0x247a506098a4   284  49c7c503000000 REX.W movq r13,0x3      ;; debug: deopt position, script offset '10610'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 3
0x247a506098ab   28b  e8b0870700     call 0x247a50682060     ;; lazy deoptimization bailout
0x247a506098b0   290  49c7c504000000 REX.W movq r13,0x4      ;; debug: deopt position, script offset '10670'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'Smi'
                                                             ;; debug: deopt index 4
0x247a506098b7   297  e8a4870300     call 0x247a50642060     ;; eager deoptimization bailout
0x247a506098bc   29c  49c7c505000000 REX.W movq r13,0x5      ;; debug: deopt position, script offset '10670'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason 'wrong map'
                                                             ;; debug: deopt index 5
0x247a506098c3   2a3  e898870300     call 0x247a50642060     ;; eager deoptimization bailout
0x247a506098c8   2a8  49c7c506000000 REX.W movq r13,0x6      ;; debug: deopt position, script offset '20971'
                                                             ;; debug: deopt position, inlining id '0'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 6
0x247a506098cf   2af  e88c870700     call 0x247a50682060     ;; lazy deoptimization bailout
0x247a506098d4   2b4  49c7c509000000 REX.W movq r13,0x9      ;; debug: deopt position, script offset '10348'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 9
0x247a506098db   2bb  e880870700     call 0x247a50682060     ;; lazy deoptimization bailout
0x247a506098e0   2c0  49c7c50a000000 REX.W movq r13,0xa      ;; debug: deopt position, script offset '10582'
                                                             ;; debug: deopt position, inlining id '-1'
                                                             ;; debug: deopt reason '(unknown)'
                                                             ;; debug: deopt index 10
0x247a506098e7   2c7  e874870700     call 0x247a50682060     ;; lazy deoptimization bailout

WHY TYPESCRIPT

More optimizable code

IDE support && type inference

Required skill nowadays

You'll write less code

function add(x: number, y: number) {
  return x + y
}

console.log(3, '2') // TypeError
function add(x, y) {
  if (typeof x !== 'number') {
    throw new Error(
      `Type ${typeof x} is not assignable for number`,
    )
  }

  if (typeof y !== 'number') {
    throw new Error(
      `Type ${typeof y} is not assignable for number`,
    )
  }

  return x + y
}

console.log(add(3, '2')) // Error: Type string is not assignable for number
index.ts
index.js

WHY TYPESCRIPT

IDE support && type inference

Required skill nowadays

You'll write less code

WHY TYPESCRIPT

WHY TYPESCRIPT

IDE support && type inference

Required skill nowadays

WHY TYPESCRIPT

WHY TYPESCRIPT

IDE support && type inference

Required skill nowadays

IDE support && type inference

Required skill nowadays

WHY TYPESCRIPT

Getting started

 ζ sudo npm i -g typescript tsc ts-node
terminal
 ζ tsc --init 
terminal
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs"
  }
}
tsconfig.json

Variable declaration

console.log(something)

var something = 5
index.ts
"use strict";
console.log(something);
var something = 5;
//# sourceMappingURL=index.js.map
index.js
console.log(something)

let something = 5
index.ts
"use strict";
console.log(something);
let something = 5;
//# sourceMappingURL=index.js.map
index.js
let age: number = 42

let name = "ahmed"

Type Annotation

Type Inference

Variable declaration

Available type annotations

  • ​number
  • string
  • boolean
  • null
  • undefined
  • void
  • ​Object
  • Array
  • Function
  • ​Any
  • Never
  • Unknown

Top-bottom types

Never
Any
Unknown
Any
let x: any = 5

x = 'ahmed'

x = [12, 3, 14]

x = {
  y: 2,
}

Top-bottom types

Unknown
let x: unknown = 5

x = 'ahmed'

x = [12, 3, 14]

x = {
  y: 2,
}

Top-bottom types

Unknown
let x: unknown

let y: number = 3

x = 'ahmed'

x = 15

y = x

All types are valid to be in place of uknown, but unknown can't be in place of a fixed type

Throws an error

Top-bottom types

Unknown
let x: unknown

let y: number = 3

x = 'ahmed'

x = 15

if (typeof x === 'number') {
 y = x
}

All types are valid to be in place of uknown, but unknown can't be in place of a fixed type

Works again ^^

Top-bottom types

Object

Object
let user: object

user = {
  username: 'ahmedosama_st',
}

user = new Date()

class User {}

user = new User()
Object
let user: {
  username: string,
  github: string,
  articles: []
}

user = {
  username: "ahmedosama_st",
  github: "https://ahmedosama-st.github.io/codefolio/",
  articles: [
    {
      id: 1,
      slug: "optimizing recursive functions",
      url: "https://dev.to/ahmedosama_st/optimizing-recursive-functions-1lhh"
    }
  ]
}

Object

Object - Optionals

Object
let user: {
  full_name: string
  github: string
  articles?: []
} = {
  full_name: 'Ahmed Osama',
  github: 'https://ahmedosama-st.github.io/codefolio/',
}

user.articles?.map((article) => console.log(article.title))

Object - Dynamic keys

Object
let user: {
  [k: string]: string
  github: string
  articles?: []
} = {
  full_name: 'Ahmed Osama',
  github: 'https://ahmedosama-st.github.io/codefolio/',
}

Object - Enum

Object
enum Privilege {
  User,
  Moderator,
  Admin,
}

console.log(Privilege.Admin)
enum OrderStatus {
  Pending,
  Delievered,
  Accepted,
  Rejected
}

Object - Destructuring

Object
let { title, url }: { title: string; url: string } = {
  title:
    'Understanding hidden classes in V8 for more optimizations',
  url: 'someblog.com/ahmedosama/v8-optimizations',
}
function sum({
  first,
  second,
}: {
  first: number
  second: number
}): number {
  return first + second
}

Array

Object
let reputations: number[] = [
  184, 129, 312, 322, 51, 98, 672,
]

const fames: number[] = reputations.filter(
  (reputation) => reputation > 125,
)

console.log(fames)

// [ 184, 129, 312, 322, 672 ]

Array - Tuples

Object
let skills: [string, number][] = [
  ['software design', 5],
  ['problem solving', 1],
  ['public speaking', 5],
  ['node.js', 5],
]

let results = skills.map(
  ([skill, score]) => `You scored ${score} in ${skill}`,
)

console.log(results)

/*
[
  'You scored 5 in software design',
  'You scored 1 in problem solving',
  'You scored 5 in public speaking',
  'You scored 5 in node.js'
]
*/

Array - Destructuring

Object
let [title, url]: [string, string] = [
  'Understanding hidden classes in V8 for more optimizations',
  'someblog.com/ahmedosama/v8-optimizations',
]
function sum([first, second]: [
  first: number,
  second: number,
]): number {
  return first + second
}

Union Types

Chossing one of many types to be stored in a variable
let rules: string | string[] = 'required'

let rules: string | string[] = ['required', 'email', 'unique']

function flipCoin(): 'heads' | 'tails' {
  if (Math.random() > 0.5) return 'heads'

  return 'tails'
}

Intersect Types

Creating new type of combining several types together in one type
let user: { username: string; password: string }

let instructor: { username: string; password: string } & {
  courses: []
}

let admin: { username: string; password: string } & {
  courses: []
} & { privilege: number }

Type Aliases

type Rule = string | string[]

let rules: Rule = 'required'

let otherRules: Rule = ['required', 'email', 'unique']
type Rule = {
  check(value: any, field?: string, data?: {}): boolean

  __toString(): string
}

type Rules = string | string[] | Rule[]

let rules: Rules = 'required'

let otherRules: Rules = ['required', 'email', 'unique']

Type Aliases

type User = { username: string; password: string }

type Instructor = User & {
  courses: []
}

type Admin = User & Instructor & { privilege: number }

Recursive Type Aliases

type Numbers = number | number[] | Numbers[]

let numbers: Numbers = [1, [2, 3, [3]]]

function sumRecursively(xs: Numbers) {
  let res: number[] = []

  if (typeof xs === 'number') {
    res.push(xs)
  } else {
    xs.flat(Infinity).map((x) => res.push(x))
  }

  return res.reduce((x, y) => x + y, 0)
}

console.log(sumRecursively(numbers)) // 9

Recursive Type Aliases

type linkedList = { value: any; next: linkedList | null }

let linkedList: linkedList = {
  value: 2,
  next: {
    value: 23,
    next: {
      value: 15,
      next: null,
    },
  },
}

Revisiting Never

The Never type is better used when you have a conditional branching and end up failing all your assertions

type Numbers = number | number[] | Numbers[]

function getSomeValue(): any {}

let x: Numbers = getSomeValue()

if (typeof x == 'number') {
  x
} else if (Array.isArray(x)) {
  x
} else {
  x
}

number

number[] | Numbers[]

never

Exhaustive
Conditionals

The Never type is better used when you have a conditional branching and end up failing all your assertions

type Numbers = number | number[] | Numbers[] | string

function getSomeValue(): any {}

let x: Numbers = getSomeValue()

if (typeof x == 'number') {
  x
} else if (Array.isArray(x)) {
  x
} else {
  x
}

number

number[] | Numbers[]

string

Exhaustive
Conditionals

Revisiting Never

class UnreachableCodeError extends Error {
  constructor(_nvr: never, message: string) {
    super(message)
  }
}

type Numbers = number | number[] | Numbers[] | string

function getSomeValue(): any {}

let x: Numbers = getSomeValue()

if (typeof x == 'number') {
  x
} else if (Array.isArray(x)) {
  x
} else {
  throw new UnreachableCodeError(
    x,
    `Expected x to be never, but x is ${typeof x}`,
  )
}

TypeError for argument of type

string to be assigned to a

a parameter of type never

Revisiting Never

Type Guards

type Lecture = {}
type Instructor = {}

type Course = {
  title: string
  path_id: number | null
  lectures: Lecture[]
  instructor: Instructor
}
let course: unknown

if (
  typeof course === 'object' &&
  course !== null &&
  'title' in course &&
  typeof course['title'] === 'string' &&
  'path_id' in course &&
  (typeof course['path_id'] === 'number' ||
    course['path_id'] === null) &&
  'lectures' in course &&
  Array.isArray(course['lectures']) &&
  'instructor' in course
) {
  course
} else {
  course
}

object but we're not

sure that's it's the

course object

unknown type

User Defined Type Guards

type Lecture = {}
type Instructor = {}

type Course = {
  title: string
  path_id: number | null
  lectures: Lecture[]
  instructor: Instructor
}
function isCourse(course: any): course is Course {
  return (
    typeof course === 'object' &&
    course !== null &&
    'title' in course &&
    typeof course['title'] === 'string' &&
    'path_id' in course &&
    (typeof course['path_id'] === 'number' ||
      course['path_id'] === null) &&
    'lectures' in course &&
    Array.isArray(course['lectures']) &&
    'instructor' in course
  )
}

let x: unknown

if (isCourse(x)) {
  x
} else {
  x
}

object but this time we're

sure that it's the course object

and get more IDE support

unknown type

type Lecture = {}
type Instructor = {}

type Course = {
  title: string
  path_id: number | null
  lectures: Lecture[]
  instructor: Instructor
}
function isCourse(course: any): asserts course is Course {
  if (
    !(
      typeof course === 'object' &&
      course !== null &&
      'title' in course &&
      typeof course['title'] === 'string' &&
      'path_id' in course &&
      (typeof course['path_id'] === 'number' ||
        course['path_id'] === null) &&
      'lectures' in course &&
      Array.isArray(course['lectures']) &&
      'instructor' in course
    )
  )
    throw new Error('Unexpected type')
}

let x: unknown

x
isCourse(x)
x

unknown type

Course

Returns void which means you can't return from this function

User Defined Type Guards

Nullish values

Null
Void
Undefined

Nullish values

Null
null in TypeScript can be though of as the 404 HTTP error, more like there's nothing existing in this variable
const user = {
  first_name: 'Ahmed',
  last_name: 'Osama',
  email: 'ahmedosama@sectheater.io',
  secondary_email: null,
}

The user never had a secondary email

Nullish values

Accessing a null value
const user = {
  first_name: 'Ahmed',
  last_name: 'Osama',
  email: 'ahmedosama@sectheater.io',
  secondary_email: null,
}

console.log(user.secondary_email.toUpperCase())
Note: "strict" option must be uncommented in tsconfig.json
Object may be null :/

Nullish values

Accessing a null value
const user = {
  first_name: 'Ahmed',
  last_name: 'Osama',
  email: 'ahmedosama@sectheater.io',
  secondary_email: null,
}

console.log(user.secondary_email!.toUpperCase())
Note: "strict" option must be uncommented in tsconfig.json
Telling typescript

to ignore if the object is null

I.E: shut up TS, I'm mature enough

Nullish values

Nullish values

undefined
undefined can be thought of as the delay of completing some task or a function. I.E. a placeholder for coming value in the future
const axios = require('axios')

let data: Post[]
;(async function getData() {
  data = await axios.get(
    'https://jsonplaceholder.typicode.com/users/1/posts',
  )
})()

console.log(data)
type Post = {
  userId: number
  id: number
  title: string
  body: string
}
undefined

Nullish values

Nullish values

undefined
undefined can be thought of as the delay of completing some task or a function. I.E. a placeholder for coming value in the future
const task = {
  created_at: new Date(),
  todo: 'lorem ipsum',
  assigned_to: 1,
  completed_at: undefined,
}
The task is not yet done by

the user holding id 1

Nullish values

Nullish values

void
Void is better used with function return types

as an indication that the return value of this function is not usable or dependent on

function returnNothing(): void {
  return null
}

Nullish values

Nullish values

void
Void is better used with function return types

as an indication that the return value of this function is not usable or dependent on

function returnNothing(): void {
  return undefined
}

Type Casting

let btn = document.getElementById('clickable')

btn.addEventListener('click', () => console.log('hello'))

console.log(btn.click())

Maybe null?

If not null, what object it is?

Compilation error
Object may be null

Type Casting

Type Casting

let btn = document.getElementById('clickable')

btn.addEventListener('click', () => console.log('hello'))

console.log(btn.click())
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>Get started</title>
  </head>
  <body>
    <p id="clickable">I'm just a text, Don't click me :(</p>
    <form>
      <button type="submit" id="button">Click me</button>
    </form>
    <script src="dist/index.js"></script>
  </body>
</html>

Type Casting

Type Casting

let btn = document.getElementById(
  'clickable',
) as HTMLInputElement

btn.addEventListener('click', () => console.log('hello'))
console.log(btn.form)

Although it might not be the real button,
but you will get the IDE support as if it's a button

Functional TypeScript

function add(x: number, y: number): number {
  return x + y
}

const add: (x: number, y: number) => number = (x, y) =>
  x + y

Function types

type MathematicalOperation = (
  x: number,
  y: number,
) => number

const add:      MathematicalOperation = (x, y) => x + y
const subtract: MathematicalOperation = (x, y) => x - y
const multiply: MathematicalOperation = (x, y) => x * y
const divide:   MathematicalOperation = (x, y) => x / y

Functions' arguments

function sum(numbers: number[]) {
  return numbers.reduce((x, y) => x + y, 0)
}

console.log(sum([1, 2, 3, 3, 4])) // 13
function sum(numbers?: number[]) {
  return numbers?.reduce((x, y) => x + y, 0)
}

console.log(sum()) // undefined

Functions' arguments

function sum(...numbers: number[]) {
  return numbers.reduce((x, y) => x + y, 0)
}

console.log(sum()) // 0
console.log(sum(1, 2, 3, 3, 4)) // 13

Functions' arguments

function parseName([first_name, last_name]: [
  string,
  string,
]): string {
  return `Hello ${first_name} ${last_name}`
}

console.log(parseName(['ahmed', 'osama'])) // Hello ahmed osama

type Point = {
  x: number
  y: number
  z?: number
}

function parsePoint({ x, y }: Point): number {
  return x + y
}

console.log(parsePoint({ x: 1, y: 2, z: 3 })) // 3

Functions overload

function add(x: number, y: number): number

function add(x: string, y: string): string

function add(x: any, y: any): any {
  return x + y
}

console.log(add(2, 3)) // 5

console.log(add('ahmed', 'osama')) // ahmedosama

console.log(add('1', 2)) // TypeError
console.log(add(undefined, null)) // TypeError

Functions overload

function add(x: number, y: number): number

function add(x: string, y: string, title: string): string

function add(
  x: number | string,
  y: string | number,
  title?: string,
): string | number {
  if (typeof x === 'number' && typeof y === 'number') {
    return x + y
  } else {
    return `${title}. ${x} ${y}`
  }
}

console.log(add(2, 3)) // 5

console.log(add('ahmed', 'osama', 'Eng')) // Eng. ahmed osama

Functions overload

function len(s: string): number
function len(arr: any[]): number

function len(x: any): number {
  return x.length
}

function len(x: string | any[]): number {
  return x.length
}

Functions' Context

type Article = {
  id: number
  slug: string
  link: string
}

type User = {
  username: string
  articles: Article[]
}

function parseArticles(this: User) {
  return this.articles.map(
    (article) =>
      `${this.username} wrote ${article.slug} you can read it from here ${article.link}`,
  )
}
let user: User = {
  username: 'ahmedosama',
  articles: [
    {
      id: 1,
      slug: 'work hard',
      link: 'someurl.com/read',
    },
  ],
}

console.log(...parseArticles.call(user))

Call signatures

type DescribableFunction = {
  description: string
  (someArg: number): boolean
}

function doSomething(fn: DescribableFunction) {
  console.log(fn.description + " returned " + fn(6));
}

Call signatures

type DescribableAdder = {
  description: string
  (x: number, y: number): number
}

function doSomething(
  fn: DescribableAdder,
  x: number,
  y: number,
) {
  console.log(fn.description + ' returned ' + fn(x, y))
}

function add(x: number, y: number): number {
  return x + y
}

add.description = 'Adds two numbers'

doSomething(add, 2, 3)

Constructor signature

type SomeConstructor = {
  new (x: number, y: number): Point
}

class Point {
  x: number
  y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

let generatePoint: SomeConstructor = Point

let p = new generatePoint(2, 3)

console.log(p)

OOP TypeScript

The OOP you know from JS is still the same here in TS but you get more features from TS

  • Typing your properties
  • Access modifiers
  • Parameter props
  • Abstract classes
  • Interfaces
  • More this management

OOP TypeScript

class Point {
  x: number
  y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  calc(): number {
    return this.x + this.y
  }
}

Typing your properties

OOP TypeScript

class Point {
  x: number
  y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  calc(): number {
    return this.x + this.y
  }
}

Property modifiers

Accessible from within the class, subclasses, and its instances

OOP TypeScript

class Point {
  protected x: number
  protected y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  calc(): number {
    return this.x + this.y
  }
}

Property modifiers

Accessible from within the class, subclasses only

OOP TypeScript

class Point {
  private x: number
  private y: number

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  calc(): number {
    return this.x + this.y
  }
}

Property modifiers

Accessible from within the class only

OOP TypeScript

class Point {
  private x: number
  private y: number

  private readonly z: number = 5

  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }

  calc(): number {
    return this.x + this.y + this.z
  }
}

Property modifiers

Equivalent to const keyword, creates an immutable property

OOP TypeScript

class Point {
  private static x: number
  private static y: number

  private readonly z: number = 5

  constructor(x: number, y: number) {
    Point.x = x
    Point.y = y
  }

  calc(): number {
    return Point.x + Point.y + this.z
  }
}

Static members

OOP TypeScript

class Point {
  private readonly z: number = 5

  constructor(private x: number, private y: number) {}

  calc(): number {
    return this.x + this.y + this.z
  }
}

Parameter properties

Inheritance

> The mechanism of deriving a class from another class that share a set of attributes and methods (Sharing functionality)

Model

Post

Tweet

Inheritance

class HTMLElement {
  public addEventListener(evt: HTMLEvent) {
    // some logic
  }
}
class HTMLInputElement extends HTMLElement {}

let evt = new HTMLEvent()
let input = new HTMLInputElement()

input.addEventListener(evt)
class HTMLButtonElement extends HTMLElement {}

let evt = new HTMLEvent()
let btn = new HTMLButtonElement()

btn.addEventListener(evt)

Inheritance

class HTMLElement {
  protected el: string = 'abc'

  public addEventListener(evt: HTMLEvent) {
    // some logic
  }
}
class HTMLInputElement extends HTMLElement {
  public getElement(): string {
    return this.el
  }
}

let input = new HTMLInputElement()

console.log(input.getElement()) // "abc"
class HTMLButtonElement extends HTMLElement {
  public getElement(): string {
    return this.el
  }
}

let btn = new HTMLButtonElement()

console.log(btn.getElement()) // "abc"

Getters && Setters

class Tweet {
  private _id: string

  constructor() {
    this._id = 'd41d8cd98f00b204e9800998ecf8427e'
  }

  public get id() {
    return this._id
  }

  public set id(newId: string) {
    this._id = require('crypto')
      .createHash('md5')
      .update(newId)
      .digest('hex')
  }
}

let t = new Tweet()

console.log(t.id) // d41d8cd98f00b204e9800998ecf8427e

t.id = 'ahmed'

console.log(t.id) // 9193ce3b31332b03f7d8af056c692b84

Getters && Setters

class User {
  private first_name?: string
  private last_name?: string

  constructor(first_name?: string, last_name?: string) {
    this.first_name = first_name
    this.last_name = last_name
  }

  get full_name(): string {
    if (
      this.first_name == undefined ||
      this.first_name == ''
    ) {
      this.first_name = 'John'
    }
    if (
      this.last_name == undefined ||
      this.last_name == ''
    ) {
      this.last_name = 'Doe'
    }

    return `${this.first_name} ${this.last_name}`
  }
}
let u = new User()
let u2 = new User('ahmed', 'osama')

console.log(u.full_name) // John Doe
console.log(u2.full_name) // ahmed osama

Overriding

> Mutating the behaviour of the parent's class method to add/change its implementation

class Parser {
  parse(data) {
    return Array.from(data)
  }
}

class JSONParser extends Parser {
  parse(data) {
    return JSON.stringify(super.parse(data))
  }
}

let p = new Parser()

let jp = new JSONParser()

const first_name = 'ahmed'

console.log(p.parse(first_name))
console.log(jp.parse(first_name))

Overriding

class Employee {
  constructor(
    private first_name: string,
    private last_name: string,
  ) {}
}

class Developer extends Employee {
  constructor(
    first_name: string,
    last_name: string,
    public salary: number,
  ) {
    super(first_name, last_name)
  }
}
index.ts
"use strict";
class Employee {
    constructor(first_name, last_name) {
        this.first_name = first_name;
        this.last_name = last_name;
    }
}
class Developer extends Employee {
    constructor(first_name, last_name, salary) {
        super(first_name, last_name);
        this.salary = salary;
    }
}
//# sourceMappingURL=index.js.map
index.js

The super call comes first before initializing any properties in a subclass

> Dependency injection is technically when one of your classes uses another class as a property of its own i.e. DI is the technique by which you can compose objects.

Credits to company.ona

Dependency Injection

class Database {
  // Dependency for your models
  public query(query: string) {
    /*
        instance is meant to be your database manager
        i.e. the MySQL/pgSQL instance that talks to the database
        driver you are working with.
        */

    this.instance.execute(query)
  }
}
class User {
  public constructor(protected db: Database) {}

  public register() {
    // Some validation logic

    this.db.create(/* Validated credentails */)
  }
}

Dependency

Client

Uses through an injector

Dependency Injection

Abstraction

> Only publish what others absolutely need to know.

Process 

abstraction

Data 

abstraction

> More abstraction means less coupling

Data Abstraction

> Keeping certain parts of your object's state hidden/enclosed within your class and not publishing them

> Not sending user's password within profile data through your API

> Keeping some properties private to the class itself and not publishing them through getters or making them protected

Process Abstraction

> Enclosing over some algorithm or a class that only provides us with an API to use this functionality

> A remote control that provides you only buttons to turn on/off TV or change channels

> A validator that gives you only exposes to you one function and keeps all other rules and validating process private

Abstract classes

abstract class Employee {
  constructor(
    private firstName: string,
    private lastName: string,
  ) {}

  abstract getSalary(): number

  get fullName(): string {
    return `${this.firstName} ${this.lastName}`
  }

  compensationStatement(): string {
    return `${
      this.fullName
    } makes ${this.getSalary()} a month.`
  }
}
class HR extends Employee {
  getSalary(): number {
    return 5
  }
}

let hr = new HR('ahmed', 'osama')

console.log(hr.compensationStatement())

> An abstract class is more like a blueprint for the classes to follow, and it can have helping members as a regular class

P.S: Abstract classes are not instantiable

Interfaces

interface Employee {
  first_name: string
  last_name: string

  getSalary(): number

  compensationStatement(): string
}
class Developer implements Employee {
  constructor(
    public first_name: string,
    public last_name: string,
  ) {}

  getSalary(): number {
    return 5
  }

  get full_name(): string {
    return `${this.first_name} ${this.last_name}`
  }

  compensationStatement(): string {
    return `${
      this.full_name
    } makes ${this.getSalary()} a month.`
  }
}

> Interfaces are a lot like abstract classes, they only cannot have methods, so they're absolute blueprints

Can only have public

access modifier

Interfaces

interface Employee {
  title: string
  getSalary(): number

  compensationStatement(): string
}
class Developer implements Employee, Person {
  constructor(
    public title: string,
    public first_name: string,
    public last_name: string,
  ) {}

  getSalary(): number {
    return 5
  }

  get full_name(): string {
    return `${this.first_name} ${this.last_name}`
  }

  compensationStatement(): string {
    return `${this.title} ${
      this.full_name
    } makes ${this.getSalary()} a month.`
  }
}

let d = new Developer(
  'Senior backend dev',
  'ahmed',
  'osama',
)

console.log(d.compensationStatement())

> Classes can implement multiple interfaces, thus, you can have one class apply multiple purposes or have many shapes

interface Person {
  first_name: string
  last_name: string

  get full_name(): string
}

Interfaces

console.log(d instanceof Developer)
console.log('title' in d) // checking for Employee implementation
console.log('full_name' in d) // checking for Person implementation

Checking if an object implements certain interface

function isPerson(object: object): object is Person {
  return (
    'first_name' in object &&
    'last_name' in object &&
    'full_name' in object &&
    typeof object['full_name'] === 'function'
  )
}

Interfaces

Interfaces can inherit from each other and they support multi inheritance

interface Employee {
  title: string
  getSalary(): number

  compensationStatement(): string
}
interface Person {
  first_name: string
  last_name: string

  get full_name(): string
}
interface Developer extends Person, Employee {
  technical_skills: [string, number][]
}
class BackendDeveloper implements Developer {
  constructor(
    public title: string,
    public first_name: string,
    public last_name: string,
    public technical_skills: [string, number][],
  ) {}

  getSalary(): number {
    return 5
  }

  get full_name(): string {
    return `${this.first_name} ${this.last_name}`
  }

  compensationStatement(): string {
    return `${
      this.full_name
    } makes ${this.getSalary()} per month`
  }
}

Interfaces

Interfaces also can be used as type aliases for

variable declarations and parameters in functions/methods

which surely an important feature that we'll use to apply the 

SOLID principles and GOF design patterns

interface User {
  first_name: string;
  last_name: string;

  get full_name(): string;
}

let user: User = {
  first_name: 'ahmed',
  last_name: 'osama',

  get full_name() {
    return `${this.first_name} ${this.last_name}`;
  },
};

Polymorphism & Encapsulation

> The mechanism by which a class or one of its members may be displayed in many forms.

> The mechanism of bundling data and their modifiers in a single entity (unit)

Generics

"Generics" as the name indicates, they are to be thought of as general types but fixed per type check

function reverse<T>(reversable: T) {
  if (typeof reversable == 'string')
    return reversable.split('').reverse().join('')
  else if (Array.isArray(reversable))
    return reversable.reverse()

  return 'Cannot be reversed'
}

I.E. Generic types can be used for creating more general typed code

Generics

Yes.. but we have the "any" type and the "unknown" what makes generics more special?

> Generic types are more like any yes, but they're fixed per type check

They're treated like parameters

for types, so the passed type

will be used as the T type

through the whole execution 

of the function/class

Another reason to use generics over any/unknown that you get more IDE support when the compiler infers the type of the passed type

Generic functions

function something<T>(arg: T): T {
  return arg
}

let x = something<number[]>([1, 2, 3, 4])

Generic functions

Generic functions/classes... etc. can have multiple generic types, maybe for input and output calculations

function something<T, U>(item: T[]): U[] {
  // return something that is array of U
}

Generic type constraints

Generic constraints are more like type checks for generic types, it would be better explained in code.

function merge<T, U>(o1: T, o2: U) {
  return {
    ...o1,
    ...o2,
  }
}

Generic type constraints

function merge<T, U>(o1: T, o2: U) {
  return {
    ...o1,
    ...o2,
  }
}

console.log(
  merge(
    { x: 1, y: 2 },
    { z: 3, t: 4 }
  )
)

Generic type constraints

function merge<T, U>(o1: T, o2: U) {
  return {
    ...o1,
    ...o2,
  }
}

console.log(
  merge(
    { x: 1, y: 2 },
    25
  )
)

TypeScript doesn't complain, cause actually type U is assignable for number

Generic type constraints

function merge<T extends object, U extends object>(
  o1: T,
  o2: U,
) {
  return {
    ...o1,
    ...o2,
  }
}

console.log(
  merge(
    { x: 1, y: 2 },
    25
  )
)

TypeScript now raises an error that the type number is not assignable for object

Generic type constraints

function extract() {}

// TODO: Implement a function
// that takes an object and a key
// and returns the value of the element
// that has the key in this object
// MUST USE GENERICS
// The following code should return
// exact same values for your function
// to be right

let p = { x: 1, y: 2 }

console.log(extract(p, 'x') === 1) // true
console.log(p.x) // undefined

Generic type constraints

function extract<T extends object, U extends keyof T>(
  dict: T,
  key: U,
): T[U] {
  let temp = dict[key]

  delete dict[key]

  return temp
}

This operator checks that the given parameter is assignable to any key that exists with the object on it's right

Notice the return type here, it's quite descriptive that you're returning the property with the key U from the object T and it's completely valid as U is meant to be keyof T

Generic classes

class Shelf<T> {}

class Book {}
class Magazine {}

let s = new Shelf<Book>()
let s2 = new Shelf<Magazine>()

Generic classes

class Collection<T> {
  private _items: { [k: string]: T } = {}

  public get items(): { [k: string]: T } {
    return this._items
  }

  public get(key: string): T {
    return this._items[key]
  }

  public set(key: string, item: T): void {
    this._items[key] = item
  }
}

Generic interfaces

interface Collection<T> {
    add(o: T): void;
    remove(o: T): void;
}
class List<T> implements Collection<T>{
    private items: T[] = [];

    add(o: T): void {
        this.items.push(o);
    }
    remove(o: T): void {
        let index = this.items.indexOf(o);
        if (index > -1) {
            this.items.splice(index, 1);
        }
    }
}

Modules

Modules in TypeScript are the same as JavaScript

But, we'll explore them anyway in case you're not familiar with them or need a refresher.

PS: Feel free to skip this topic

What is a module?

> Helps you make your code more portable

> You can make certain parts of your code private and enclosed within the module

> Modules are always evaluated in strict mode

What makes a module?

> Modules need a server to be loaded

> Modules are deffered to not block page rendering

> Modules are always evaluated in strict mode

Exporting code

The export keyword is used for sharing some parts of your code outside of your module to be used by other modules

export enum Size {
  'small',
  'medium',
  'large',
}
export enum Color {
  'green',
  'red',
  'blue',
}

Importing code

Yeah, the opposite of export.

It brings code from other modules into current module

import { Size } from './Size'
import { Color } from './Color'

Default export

Surely it exports code, but the default keyword makes it some sort of a special export

that you can import without destructuring

as the name indicates, it's the default export

import sayWelcome from './sayWelcome'
export default function sayWelcome() {
  return "hello :D"
}

Aliased imports

You can export a huge object from a module

and destructure what you want when importing

or you can import the whole module and alias it

import * as _ from './Module';

console.log(_);
const double = x => x * 2
const square = x => x * x

export {double, square}

Would be the same

if export keyword

is next to consts

Decorators

Decorators as the name suggests, are modifiers

They can be used to add more functionality to certain parts of your code.

class Component {
  @useState<number>(0)
  count: number;
  setCount: Function;
}

Decorators

There exist 4 types of decorators you can leverage in typescript

  • Class decorator
  • Method decorator
  • Property decorator
  • Parameter decorator

Class Decorator

There exist 5 types of decorators you can leverage in typescript

  • Class decorator
  • Method decorator
  • Property decorator
  • Parameter decorator
  • Accessor decorator
declare type ClassDecorator = <TFunction extends Function>(
  target: TFunction,
) => TFunction | void;
const log: ClassDecorator = (target: any): void => {
  console.log(target);
};

// function log(target: any): void {
//   console.log(target);
//   console.log(`Hello from ${target.name}`);
// }

@log
class User {}
@log
class Database {}

Targetted class/function

Method Decorator

There exist 5 types of decorators you can leverage in typescript

  • Class decorator
  • Method decorator
  • Property decorator
  • Parameter decorator
  • Accessor decorator
declare type MethodDecorator = <T>(
  target: Object,
  propertyKey: string | symbol,
  descriptor: TypedPropertyDescriptor<T>,
) => TypedPropertyDescriptor<T> | void;
function auth(token: string): MethodDecorator {
  return function (
    target: Object,
    prop: string,
    desc: PropertyDescriptor,
  ): void {};
}


class UserController {
  @auth('random-jwt-token-maybe')
  changePassword() {/* Some process */}
}

Property attributes

[[Value]]: [Function]

[[Writable]]: true

[[Enumerable]]: true

[[Configurable]]: true

Name of the property

Property Decorator

declare type PropertyDecorator = (
  target: Object,
  propertyKey: string | symbol,
) => void;
const logProp: PropertyDecorator = (
  target: Object,
  prop: string,
) => {
  console.log(target, prop);
};

class Playground {
  @logProp
  private first_name: string = 'ahmed';
}

Parameter Decorator

declare type ParameterDecorator = (
  target: Object,
  propertyKey: string | symbol,
  parameterIndex: number,
) => void;
const logParam: ParameterDecorator = (
  target: Object,
  key: string,
  index: number,
) => {
  console.log(key, index);
};

class UserController {
  public index(@logParam request) {}
}
class Component {
  @useState<number>(0)
  count: number;
  setCount: Function;
}

Decorator use cases

useState

class Component {
  @useState<number>(0)
  count: number;
  setCount: Function;
}
function useState<T>(seed: T): PropertyDecorator {
  return function (target: Object, key: string): void {
    target[key] = seed;

    target[
      `set${key.replace(/^\w/, (c) => c.toUpperCase())}`
    ] = (val: T) => (target[key] = val);
  };
}

Dependency Injection

Revisting dependency injection

IoC Containers

So, our goal here is to achieve DI using decorators

Alright, let's start by creating our

Dependency Container

Represents the storage of our dependencies

We need a storage like caching our dependencies

We need a way to extract out an item from cache

We need only one instance from container

Dependency Injection

class Container<T extends Object> {
  private _providers: Map<string, T> = new Map();

  get providers() {
    return this._providers;
  }
}

Dependency Injection

import { find } from 'lodash';

class Container<T extends Object> {
  public resolve(token: string) {
    const matchedProvider = find(
      this._providers,
      (_provider, key) => key === token,
    );

    if (matchedProvider) {
      return matchedProvider;
    } else {
      throw new Error(`No provider found for ${token}!`);
    }
  }
}

We need lodash

cause "providers" is object

and we can't easily iterate

over it

Up next, we need to export a single instance of our class a.k.a Singleton

Dependency Injection

import { find } from 'lodash';

class Container<T extends Object> {
  private _providers: Map<string, T> = new Map();

  get providers() {
    return this._providers;
  }

  public resolve(token: string) {/*Some logic*/}
}

export const container = new Container();

Now we need to mark classes as prepared for injection a.k.a Injectable

A little hack until we discover the Singleton design pattern

Dependency Injection

import { container } from './Container';

export function Injectable(token: string): Function {
  return function (target: { new () }): void {
    container.providers[token] = new target();
  };
}

Class decorator

Constructor type

Dependency Injection

import { container } from './Container';

export function Inject(token: string): PropertyDecorator {
  return function (target: any, key: string): void {
    Object.defineProperty(target, key, {
      get: () => container.resolve(token),
      enumerable: true,
      configurable: true,
    });
  };
}

We're either defining a new property

or modifying existing one

Dependency Injection

import { Inject } from './Inject';
import { Injectable } from './Injectable';

@Injectable('db')
class Database {
  sayWelcome() {
    return 'hello';
  }
}

@Injectable('user')
class User {
  @Inject('db') public db;
}

let u = new User(); // no need for constructor

console.log(u.db.sayWelcome()); // hello

Dependency Injection

import axios from 'axios';

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

class Blog {
  posts: Post[] | unknown;
}

let b = new Blog();

axios
  .get('https://jsonplaceholder.typicode.com/posts')
  .then((res) => {
    b.posts = res.data;
    console.log(b.posts);
  });

Building a fetch decorator

interface Post {
  userId: number;
  id: number;
  title: string;
  body: string;
}

class Blog {
  @Get('https://jsonplaceholder.typicode.com/posts')
  posts: Promise<Post[]>;
}

let b = new Blog

b.posts.then(console.log) // API posts

Building a fetch decorator

function Get(url: string): PropertyDecorator {
  return function (target: Object, prop: string): void {
    Object.defineProperty(target, prop, {
      get: function () {
        return axios.get<Post[]>(url).then((res): Post[] => res.data);
      },
    });
  };
}

Building a fetch decorator

What if we want an item with certain id?

function Get(url: string, id?: number): PropertyDecorator {
  return function (target: Object, prop: string): void {
    Object.defineProperty(target, prop, {
      get: function () {
        return axios
          .get<Post[]>(url)
          .then(function ({
            data,
          }: {
            data: Post[];
          }): Post[] | Post {
            if (id) {
              return data.find((item) => item.id === id);
            }

            return data;
          });
      },
    });
  };
}

Building a fetch decorator

function Get(url: string, id?: number): PropertyDecorator {
  return function (target: Object, prop: string): void {
    Object.defineProperty(target, prop, {
      get: async (): Promise<Post | Post[]> => {
        const { data } = await axios.get<Post[]>(url);

        if (id) {
          return data.find((item) => item.id === id);
        }

        return data;
      },
    });
  };
}

Building a fetch decorator

What if we want to limit the result?

function Get({
  url,
  id,
  limit,
}: {
  url: string;
  id?: number;
  limit?: number;
}): PropertyDecorator {
  return function (target: Object, prop: string): void {
    Object.defineProperty(target, prop, {
      get: async (): Promise<Post | Post[]> => {
        const { data } = await axios.get<Post[]>(url);

        if (id) {
          return data.find((item) => item.id === id);
        }

        if (limit) {
          return data.slice(0, limit);
        }

        return data;
      },
    });
  };
}

Building a fetch decorator

Object destructuring can be like named parameters from other languages

Wrapping Up

Wrapping Up

  • What is typescript and why to use it
  • Basic types in typescript
  • Functions and functional types
  • OOP code in typescript
  • Generic types are your besties
    • Required, Readonly
    • Partial, Pick, Omit
  • Decorators are extremely powerful

Wrapping Up

SecTheater

  • What is typescript and why to use it
  • Basic types in typescript
  • Functions and functional types
  • OOP code in typescript
  • Generic types are your besties
    • Required, Readonly
    • Partial, Pick, Omit
  • Decorators are extremely powerful

SecTheater

Raise up your techinal skills

SecTheater

Baby steps in TypeScript to seniority

By Security Theater

Baby steps in TypeScript to seniority

Ever wondered why the WATs of JS occur? Join me in this journey of understanding JavaScript foundation where we learn things like Type system and Coercion and many more interesting topics.

  • 57