Book Image

TypeScript 4 Design Patterns and Best Practices

By : Theofanis Despoudis
Book Image

TypeScript 4 Design Patterns and Best Practices

By: Theofanis Despoudis

Overview of this book

Design patterns are critical armor for every developer to build maintainable apps. TypeScript 4 Design Patterns and Best Practices is a one-stop guide to help you learn design patterns and practices to develop scalable TypeScript applications. It will also serve as handy documentation for future maintainers. This book takes a hands-on approach to help you get up and running with the implementation of TypeScript design patterns and associated methodologies for writing testable code. You'll start by exploring the practical aspects of TypeScript 4 and its new features. The book will then take you through the traditional gang of four (GOF) design patterns in their classic and alternative form and show you how to use them in real-world development projects. Once you've got to grips with traditional design patterns, you'll advance to learning about their functional programming and reactive programming counterparts and how to couple them to deliver better and more idiomatic TypeScript code. By the end of this TypeScript book, you'll be able to efficiently recognize when and how to use the right design patterns in any practical use case and gain the confidence to work on scalable and maintainable TypeScript projects of any size.
Table of Contents (14 chapters)
1
Section 1: Getting Started with TypeScript 4
4
Section 2: Core Design Patterns and Concepts
8
Section 3: Advanced Concepts and Best Practices

Using VSCode with TypeScript

You now know what libraries are included in the code examples and how to run them. Just as it is important to know how to use the examples in this book, it is of equal importance to master the editor and the development environment. This is because using an Integrated Development Environment (IDE) can help you maximize your time when you're debugging or refactoring methods or functions.

First, you will learn how to use VSCode for this book's code. This will help you not only run and debug the examples, but experiment with the code as well. You can use the IDE's inspection utilities to view the inferred types of each defined variable. Finally, you want to understand how to refactor existing code so that you can make it easier to read and reuse.

Using VSCode for this book's code

VSCode is a lightweight integrated editor that was released in 2015 by Microsoft. It offers an impressive array of features that aid us when writing code. It currently supports several major programming languages, including TypeScript, Java, Go, and Python. We can use VSCode's native TypeScript integration to write and debug code, inspect types, and automate common development tasks. Let's get started:

  1. To install it, you may want to visit the official Download page at https://code.visualstudio.com/Download and choose the right executable for your operating system. In this book, we are using VSCode version 1.53.1.
  2. Once installed, you will want to open this book's projects folder using the menu dialog: File | Open | (Project). Since we are working on the first chapter, you can expand the Chapter 1 folder and inspect the programs located there.
  3. We have preconfigured all the necessary tasks and launch configurations to run or debug the examples in this book. If you've installed all the project dependencies, as we did in the previous section, the only thing you need to do is select the Run icon from the sidebar and select the Run Code from Chapter 1 option, as depicted in the following screenshot:
    Figure 1.1 – Run Code from Chapter 1 option

    Figure 1.1 – Run Code from Chapter 1 option

  4. You will be prompted to select a program name to run. For now, let's run computeFrequency.ts. This contains a function that computes the frequency Map of an input string:
    function computeFrequency(input: string) {
      const freqTable = new Map();
      for (let ch of input) {
        if (!freqTable.has(ch)) {
          freqTable.set(ch, 1);
        } else {
          freqTable.set(ch, freqTable.get(ch) + 1);
        }
      }
      return freqTable;
    }
     
    console.log(computeFrequency("12345"));

    The result will be shown in the console:

    Map(5) {1 => 1, 2 => 1, 3 => 1, 4 => 1, 5 => 1}
  5. You can run several of the examples in this book or create additional programs on your own. You can also debug any section of the code. To do that, you need to add breakpoints next to each line, as depicted in the following screenshot:
    Figure 1.2 – Debugging code

    Figure 1.2 – Debugging code

  6. Once you've placed breakpoints, you can run the task again and the code will stop at the first one. Debugging with breakpoints is valuable when you're trying to understand how the program behaves at runtime.
  7. You can also add more programs to select from the launch list. To do this, you must open the.vscode/launch.json file and modify the inputs-> programNameChapter1-> options to include an additional filename. For example, if you have created a new file named example.ts, you will need to change the inputs field so that it looks like this:
    "inputs": [
        {
          "type": "pickString",
          "id": "programNameChapter1",
          "description": "What program you want to 
             launch?",
          "options": [
            "computeFrequency.ts",
            "removeDuplicateVars.ts",
            "example.ts",
          ],
          "default": "computeFrequency.ts"
        }
    ]

From now on, you will be able to select this program from the launch list.

Inspecting types

Now that you know how to run and debug programs using VSCode, you probably want to know how to inspect types and apply suggestions to improve consistency.

By default, when you write statements in VSCode, they retrieve suggestions and other operations from the TypeScript language server. This server is bundled together with the tsc compiler and offers an API for querying or performing those refactorings. You don't have to run or configure this server manually as VSCode will do that for you.

Let's learn how to inspect types using VSCode:

  1. Open the removeDuplicateChars.ts file in the editor. This contains a function that accepts an input string and removes any duplicate characters. Feel free to run it and inspect how it works:
    function removeDuplicateChars(input: string) {
      const result: string[] = [];
      // const result = [];
      let seen = new Set();
      for (let c of input) {
        if (!seen.has(c)) {
          seen.add(c);
          result.push(c);
        }
      }
    }
    console.log(removeDuplicateChars("aarfqwevzxcddd"));
  2. If you place the mouse cursor on top of the variables in the function body, you can inspect their types. Here is an example of using the result variable:
     Figure 1.3 – Inspecting the type of a variable

    Figure 1.3 – Inspecting the type of a variable

    This is fairly obvious as we declared its type. However, we can inspect types that have been inferred by the compiler and figure out when or why we need to add explicit types.

    What happens when you don't explicitly add types to variables that need them? In most cases, the compilation will fail.

  3. Comment this line and remove the comments from the following line:
    const result = [];
  4. Then, run the program again. You will see the following error:
    removeDuplicateVars.ts:8:19 - error TS2345: Argument of type 'string' is not assignable to parameter of type 'never'.

    If you inspect the type again, you will see that TypeScript will infer it as never[]:

    Figure 1.4 – The never type

    Figure 1.4 – The never type

    A never type is almost always what you don't want. The compiler here could not determine the correct type at instantiation, even though we pushed string characters into the for loop's body.

  5. If we were to initialize the result with a value, the compiler will infer it correctly; for example:
Figure 1.5 – Inferred type

Figure 1.5 – Inferred type

Using the correct types and relying on type inference whenever possible is very important when working with TypeScript. VSCode offers good inspection utilities to do this, but a lot of times, we need to help the compiler do this.

You will learn how to work with types and understand type inference in Chapter 2, TypeScript Core Principles, in the Working with advanced types section.

Refactoring with VSCode

Using VSCode, we can refactor the code that we are working with. Code refactoring is the process of restructuring the code base to accommodate future changes. With refactoring, we have specific end goals, such as making the code easier to read, easier to extend, or easier to navigate while keeping the same functionality.

Note

When you perform refactoring, you want to have unit tests in place before changing any existing code. This is to ensure you did not introduce any breaking changes or fail to capture edge cases.

In some cases, refactoring code can reveal potential opportunities for using design patterns, so it's a useful technique to learn. The main gotcha is that when you refactor, you need to know when to stop. Once you've applied simple refactoring, you should stop and think whether further changes to the code base are justified based on the scope of the problem you are trying to solve.

To perform simple refactoring with VSCode, you just need to highlight a specific block of code and review the options:

  1. Inside the Chapter 1 source code folder, open the refactoring.ts file. You will find a definition of the find function that implements a linear search algorithm to find the elements inside a list:
    function find<T>(arr: T[], predicate: (item: T) => boolean) {
      for (let item of arr) {
        if (predicate(item)) {
          return item;
        }
      }
      return undefined;
    }

    Notice that we can refactor the predicate function parameter and use it as the same type with the indexOf function parameter. You just need to select the whole function body; that is, (item: T) => Boolean.

  2. Right-click and select the Refactor option. Then, select Extract to type alias:
    Figure 1.6 – Extract to type alias option

    Figure 1.6 – Extract to type alias option

  3. Name it Predicate. This will create a type alias for this function signature:
    type Predicate<T> = (item: T) => boolean;
  4. Now, you can see that the IDE has renamed the type of this variable as the refactored definition:
    function indexOf<T>(arr: T[], predicate: Predicate<T>) {
      for (let i = 0; i < arr.length; i += 1) {
        if (predicate(arr[i])) {
          return i;
        }
      }
      return -1;
    }

    What is the inferred return type of this function?

    The answer is T | undefined because we can either find the element, thus returning it, or not find it and return it undefined.

Reusing types and blocks of code like this helps you compartmentalize the code base and makes it easier to reuse.

VSCode offers additional refactoring options, such as the following:

  • Extract Method: When you want to extract a block of code into a method or a global function.
  • Extract Variable: When you want to extract an expression as a variable.
  • Rename Symbols: When you want to rename a variable and all its usages across files.

Familiarizing yourself with these refactoring operations can help you save time and reduce typos when modifying code. In the next section, you will learn how to use Unified Modeling Language (UML) to visualize object-oriented systems.