Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.
Gregor Woiwode edited this page Oct 18, 2018 · 33 revisions

TypeScript In Depth

1. Preparation | Warm up

  • Create a directory at your system where you store you coding samples
  • Initialize npm (see the command in the code sample below)
  • Start your editor
# somewhere in your system
mkdir typescript-workshop
cd typescript-workshop

# inside typescript-workshop/
npm init -y

# create a directory where all exercise will be saved
mkdir src

# Start VS Code
code .

2. TS Config | Basic configuration

  • Create a tsconfig.json in the root of the folder you created initially for your workshop.
  • Add the following content to it
{
  "compilerOptions": {
    "module": "commonjs",
    "target": "es5"
  }
}

3. Hello TypeScript

  • Create a directory src that will contain all your samples
  • Inside src/ create another directory 1-welcome containing an index.ts file
  • Add the following code
// src/1-welcome/index.ts

const greeting = 'Hello TypeScript';

console.log(greeting);
  • Afterwards compile and run the code with node
# Transpile TypeScript to JavaScript
tsc ./src/1-welcome/index.ts

# Execute transpiled JavaScript-Source
node ./src/1-welcome/index.js

4. Basic Types | Simple Task List

  • Create a directory task-board/
  • Inside the directory task-board/, create a file index.ts
  • Add the following properties to the index.ts and initialise them with some default values
    • const tasks: any[]
    • const id: string
    • const title: string
    • const text: string
    • const priority : number
    • const isDone: boolean
  • Implement a method that takes all parameters above except of tasks.
  • Create an object from the passed arguments and adds it to the list tasks.
addTaskToList(id, title, text, ...) {
  const task = { ... };
  tasks.push(task);
}

5. Enum Introduction

  • Create a directory enum/
  • Inside the directory enum/, create a file index.ts
  • Create an enum and log it’s values
  • 👓 Have a look at the compiled code
  • Try to log the names of the enum entries to the console
enum Priority {
  Low,
  Medium,
  High
}

console.log(Priority.Low);
// TODO: Log the names of all enum entries

6. Task List | Use an Enum

  • Instead of passing a number introduce an Enum called TaskPriority having the values Low, Medium, High
  • You may expect that the method addTaskToList breaks now, but it is not. Think about why your code still works.
  • Write a method getSortedDescByPriority that takes the task array. * It should sort the task in descendant order by the priority property meaning that the highest priority comes first.

7. Task List | String Enum

  • Turn your number enum into a string enum
  • Adjust getSortedDescByPriorityto work with the new enum values. * Hint: The string method localeCompare helps sorting
// task-board/index.ts
enum TaskPriority {
  Low = 'Low',
  Medium = 'Medium',
  High = 'High'
}

8. Never | Exhaustiveness Check

  • Add a function that returns due dates based on the priority of each task. _ Low => ’Sometimes` _ Medium => ’Today’ * High => ‘Now’
  • Implement the needed logic using a switch-case-statement
  • Add the exhaustiveness to the default path of the switch-case-statement
  • Afterwards add a new priority “VeryHigh” to the enum TaskPriority. Note that your program will not compile until you add the missing priority to the switch-case statement.

    Think How does TypeScript achieve that?

function assertNever(value: never): never {
  throw new Error(`Expected never to be called but got ${value}`);
}

// ...
function dueDates(tasks: any[]) {
  return tasks.map(task => {
    switch (task.priority) {
      case TaskPriority.Low:
        return 'Sometimes';
      // ...
      default:
        assertNever(task.priority);
    }
  });
}

9. Task | Introduce Interface

  • Add an interface Task
  • Add all known properties to it
  • Use that interface everywhere in the code _ addTaskToList _ getSortedDescByPriority * dueDates

10. Class

  • Create the file todo-list.ts inside task-board/models and add a class called TaskList.
  • Add the method addTaskToList to that class.
  • Add the method dueDates to that class.
  • Add the method getSortedDescByPriority to that class.
  • Make tasks a member of TaskList.
  • Now task-board/index.ts should just use the API of TaskList and may do some console.logs.

11. Barrels

  • Create an index.ts File in task-board/models.
  • Export all the files from models/ using your barrel.
  • Simplify the import statements in your task-board/index.ts
// task-board/models/index.ts
export * from './your-model.ts';
export * from './your-other-model.ts';
// task-board/index.ts
import { TaskList, Task } from './models';

// Use TaskList API...

12. tsconfig | outDir

  • Specify the outDir parameter in your tsconfig.json
  • Compile the task-board source
    • tsc --project ./src/task-board/tsconfig.json
  • Run the compiled code to see if everything still works fine.
    • node ./src/task-board/dist
  • Finally, add dist/ to your .gitconfig
    • You never want to commit and push the compilation result.

13. Debugging | Node Inspector

  • Configure the TypeScript Compiler to emit SourceMaps.

  • Open your Chrome Browser and enter chrome://inspect/#devices address field

  • Open the displayed remote target by clicking the link inspect.

  • Set a few break points by clicking on the line numbers in editor view of the developer tools.

    chrome-inspect

{
  "compilerOptions": {
    // ...
    "inlineSourceMap": true
  }
}

14. Debugging | Visual Studio Code

  • Create the directory .vscode inside your root folder
  • Create launch.json inside .vscode/
  • Apply the following settings to launch.json.
  • Switch to VS-Codes Debug View (CTRL+SHIFT+D)
  • Select the Configuration Taskboard from the select box and press the green arrow button to execute your code.
  • Set at least one breakpoint inside index.ts and try to debug that file.
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Taskboard",
      "program": "${workspaceFolder}/src/task-board/dist/index.js"
    }
  ]
}

vscode-inspect

15. Discriminated Unions | Enhance task board priorities

  • Create two union types to build two catagories of tasks
    • UrgentTasks
    • OptionalTasks
  • When a task has the priority High or VeryHigh it is an UrgentTask
  • When a tas has the priority Low or Medium it is a MandatoryTask.
  • Add a new method to the TaskList called addUrgentTask that accepts a Task and UrgentTask as priority
class TaskList {
  // ...
  addUrgentTask(task: Task, priority: TaskPriority) {
    const urgentTask = { ...task, priority };
    // add urgentTask to List
  }
}

16. Write your first Type Guard

  • Introduce a private method to TaskList called isUrgent()
  • Check whether a task is urgent or mandatory by checking it's priority property
  • After that, have a look how TypeScript infers the type using your Type Guard
  • Write another method getUrgent that yields a filtered list of tasks that must have an urgent priority
    • Use the Type Guard to filter the tasks.
class TaskList {
  // ...

  private isUrgent(priority: TaskPriority): priority is UrgentTask {
    // yield true if an urgent priority is detected.
  }
}

18. Provide tasks as readonly dictionary

  • Provide a property tasks in TaskList
  • tasks should be a dictionary where all properties of each tasks are readonly.
    • [key: string]: /* readonly type for task */

You want that tasks can be read from outside but you want to prevent changes to your dictionary.

  • Create a mapped type that transforms each property of Task to a readonly property.
    • Hint Use the readonly keyword
// Lookup type of a property
type taskId = Task['id']; // string

// iterate through properties
type taskProps = { [prop in keyof Task] };

// transform property types
type optionalTaskProperties = { 
  [prop in keyof Task]?: Task[prop];
};

19. Configure a path alias

  • Add a path alias to your project task-board
  • Afterwards, refactor the import path in index.ts to use the alias instead of the relative path.
  • Check if everything still works by compiling and running your source code.
{
"compilerOptions": {
  // ...

  "baseUrl": ".",
  "paths": {
    "@models/*": [
      "./models/*"
    ]
  }
 }
}
Clone this wiki locally