TypeScript Tutorial: Building a Simple Interactive Typing Tutor

In the digital age, typing proficiency is a crucial skill. Whether you’re a student, a programmer, or a professional, the ability to type quickly and accurately can significantly boost your productivity and efficiency. Yet, many struggle with this fundamental skill, often relying on hunt-and-peck methods that slow them down. This tutorial will guide you through building a simple, interactive typing tutor using TypeScript, a powerful superset of JavaScript. This project will not only help you learn TypeScript but also provide a practical tool to improve your typing skills.

Why Build a Typing Tutor?

There are several reasons why building a typing tutor is a great project for learning TypeScript:

  • Practical Application: You’ll create something useful, providing real-world value.
  • Interactive Nature: Typing tutors involve user input and feedback, making them engaging and dynamic.
  • Clear Scope: The project’s scope is manageable, perfect for beginners and intermediate developers.
  • TypeScript Fundamentals: You’ll practice core TypeScript concepts like types, interfaces, classes, and event handling.

By the end of this tutorial, you’ll have a fully functional typing tutor that you can use to practice and improve your typing speed and accuracy. You’ll also gain a solid understanding of how to apply TypeScript in a practical, interactive application.

Setting Up Your Development Environment

Before we dive into the code, let’s set up your development environment. You’ll need the following:

  • Node.js and npm (Node Package Manager): Used to manage project dependencies and run your TypeScript code. Download and install from nodejs.org.
  • A Code Editor: A code editor like Visual Studio Code (VS Code), Sublime Text, or Atom. VS Code is highly recommended due to its excellent TypeScript support.
  • TypeScript Compiler: We’ll install this locally in our project.

Let’s create a new project directory and initialize it with npm:

mkdir typing-tutor
cd typing-tutor
npm init -y

This will create a `package.json` file in your project directory. Now, install TypeScript and a few other necessary packages:

npm install typescript --save-dev
npm install --save-dev @types/node

Next, we’ll initialize a TypeScript configuration file (`tsconfig.json`):

npx tsc --init

This command creates a `tsconfig.json` file in your project directory. This file configures how the TypeScript compiler behaves. You can customize it as needed, but for this tutorial, the default settings will suffice. You might want to modify the `outDir` to specify where the compiled JavaScript files will be placed. For example, set `”outDir”: “./dist”`.

Finally, create a file named `index.ts` in your project directory. This will be where we write our TypeScript code.

Project Structure

Before we start coding, let’s outline the project structure. This will help us organize our code and make it easier to understand and maintain.

typing-tutor/
├── index.ts           // Main TypeScript file
├── tsconfig.json      // TypeScript configuration file
├── package.json       // Node.js package file
└── dist/              // (Optional) Output directory for compiled JavaScript files

Coding the Typing Tutor

Now, let’s start writing the code for our typing tutor. We’ll break it down into manageable sections to make it easier to follow.

1. Setting Up the HTML Structure

First, we need to create the HTML structure for our typing tutor. This will define the layout and the elements that will be displayed on the screen. We’ll create a simple `index.html` file in the project’s root directory. Here’s what that might look like:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Typing Tutor</title>
    <style>
        body {
            font-family: sans-serif;
            text-align: center;
        }
        #typing-area {
            font-size: 2em;
            margin: 20px 0;
        }
        .correct {
            color: green;
        }
        .incorrect {
            color: red;
        }
    </style>
</head>
<body>
    <h1>Typing Tutor</h1>
    <div id="typing-area"></div>
    <input type="text" id="input-field" autofocus>
    <script src="./dist/index.js"></script>
</body>
</html>

This HTML sets up the basic layout:

  • A heading (`<h1>`) for the title.
  • A `div` with the id “typing-area” to display the text to be typed.
  • An `input` field (`<input type=”text”>`) where the user will type.
  • A `<script>` tag to include the compiled JavaScript file (`index.js`).

Also, the CSS is included in the head tag to style the elements. For instance, the styles for the “correct” and “incorrect” classes will be used to highlight the typed letters.

2. Basic TypeScript Setup (index.ts)

Now, let’s write the initial TypeScript code in `index.ts`:

// index.ts

const typingArea = document.getElementById('typing-area') as HTMLElement;
const inputField = document.getElementById('input-field') as HTMLInputElement;

// Sample text to type
const textToType = "The quick brown fox jumps over the lazy dog.";

// Display the text
typingArea.textContent = textToType;

// Focus the input field when the page loads
inputField.focus();

In this code:

  • We get references to the `typingArea` and `inputField` HTML elements using `document.getElementById`. The `as HTMLElement` and `as HTMLInputElement` are type assertions, which tell TypeScript the specific types of these elements.
  • We define a `textToType` string, which is the text the user will type.
  • We set the `textContent` of the `typingArea` to the `textToType` string.
  • We focus the `inputField` to allow the user to start typing immediately.

3. Handling User Input

Next, we’ll add an event listener to the `inputField` to listen for user input. We’ll compare the user’s input with the expected text and provide feedback.

// index.ts (continued)

let currentIndex = 0;

inputField.addEventListener('input', (event: Event) => {
  const input = (event.target as HTMLInputElement).value;
  const currentCharacter = textToType[currentIndex];

  if (input[input.length - 1] === currentCharacter) {
    // Correct character
    inputField.style.color = 'green';
    currentIndex++;
  } else {
    // Incorrect character
    inputField.style.color = 'red';
  }

  // Clear the input field after each character
  if (input.length > 1) {
    (event.target as HTMLInputElement).value = input[input.length - 1];
  }

  // Check for completion
  if (currentIndex === textToType.length) {
    alert('Congratulations! You finished!');
    currentIndex = 0;
    (event.target as HTMLInputElement).value = '';
    inputField.focus();
  }
});

In this code:

  • We declare a `currentIndex` variable to keep track of the current character the user is expected to type.
  • We add an event listener to the `inputField` for the “input” event, which fires whenever the user types something.
  • Inside the event listener:
    • We get the current input value using `(event.target as HTMLInputElement).value`.
    • We get the character at the `currentIndex` from the `textToType` string.
    • We compare the last character entered by the user with the expected character.
    • If they match, we change the input field’s color to green and increment `currentIndex`.
    • If they don’t match, we change the input field’s color to red.
    • We clear the input field after each character to make it simpler.
    • We check if the user has completed typing the entire text. If so, we display a congratulatory message, reset the `currentIndex`, clear the input field, and refocus.

4. Improving Feedback and User Experience

Let’s refine the feedback and user experience by highlighting correct and incorrect characters directly in the `typingArea`.

// index.ts (continued)

function updateTypingArea() {
  let highlightedText = '';
  for (let i = 0; i < textToType.length; i++) {
    if (i < currentIndex) {
      // Typed correctly
      highlightedText += `<span class="correct">${textToType[i]}</span>`;
    } else if (i === currentIndex) {
      // Current character (to be typed)
      highlightedText += `<span class="current">${textToType[i]}</span>`;
    } else {
      // Remaining characters
      highlightedText += textToType[i];
    }
  }
  typingArea.innerHTML = highlightedText;
}

inputField.addEventListener('input', (event: Event) => {
  const input = (event.target as HTMLInputElement).value;
  const currentCharacter = textToType[currentIndex];

  if (input[input.length - 1] === currentCharacter) {
    currentIndex++;
    (event.target as HTMLInputElement).value = ''; // Clear input
    updateTypingArea();
  } else {
    // Incorrect character
    (event.target as HTMLInputElement).value = input[input.length - 1]; // Keep the last character
  }

  // Check for completion
  if (currentIndex === textToType.length) {
    alert('Congratulations! You finished!');
    currentIndex = 0;
    (event.target as HTMLInputElement).value = '';
    updateTypingArea();
    inputField.focus();
  }
});

// Initial call to updateTypingArea
updateTypingArea();

Here’s what changed:

  • We added a function `updateTypingArea()` that dynamically generates the HTML for the `typingArea`.
  • Inside `updateTypingArea()`:
    • It iterates through `textToType`.
    • If a character has been typed correctly, it’s wrapped in a `<span class=”correct”>`.
    • If it’s the current character, it’s wrapped in a `<span class=”current”>`.
    • Otherwise, it’s displayed as plain text.
  • We call `updateTypingArea()` initially to display the text.
  • Inside the input event listener:
    • After a correct character, we clear the input field and call `updateTypingArea()` to update the display.
  • After the incorrect character, we keep the last character entered.

Don’t forget to add the CSS for the new classes to your `index.html`:


.correct {
    color: green;
}
.incorrect {
    color: red;
}
.current {
    text-decoration: underline;
}

5. Adding a Reset Functionality

To make the typing tutor more user-friendly, let’s add a reset button that allows the user to start over at any time.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Typing Tutor</title>
    <style>
        body {
            font-family: sans-serif;
            text-align: center;
        }
        #typing-area {
            font-size: 2em;
            margin: 20px 0;
        }
        .correct {
            color: green;
        }
        .incorrect {
            color: red;
        }
        .current {
            text-decoration: underline;
        }
        #reset-button {
            margin-top: 10px;
            padding: 10px 20px;
            font-size: 1em;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1>Typing Tutor</h1>
    <div id="typing-area"></div>
    <input type="text" id="input-field" autofocus>
    <button id="reset-button">Reset</button>
    <script src="./dist/index.js"></script>
</body>
</html>

Add a button to your HTML:

<button id="reset-button">Reset</button>

And then add the corresponding code to your TypeScript file:

// index.ts (continued)

const resetButton = document.getElementById('reset-button') as HTMLButtonElement;

function resetTyping() {
  currentIndex = 0;
  inputField.value = '';
  updateTypingArea();
  inputField.focus();
}

resetButton.addEventListener('click', resetTyping);

In this code:

  • We get a reference to the reset button.
  • We define a `resetTyping()` function that resets the `currentIndex`, clears the input field, updates the display, and focuses the input field.
  • We add a click event listener to the reset button that calls `resetTyping()`.

6. Adding Typing Speed and Accuracy Metrics

To make the typing tutor more informative, we can add metrics to track typing speed (words per minute – WPM) and accuracy.

// index.ts (continued)

let startTime: number | null = null;
let typedChars = 0;
let correctChars = 0;

const wpmDisplay = document.createElement('div');
wpmDisplay.id = 'wpm-display';
wpmDisplay.style.marginTop = '10px';
document.body.appendChild(wpmDisplay);

const accuracyDisplay = document.createElement('div');
accuracyDisplay.id = 'accuracy-display';
accuracyDisplay.style.marginTop = '5px';
document.body.appendChild(accuracyDisplay);

function calculateWPM(): void {
  if (startTime) {
    const elapsedTime = (Date.now() - startTime) / 60000; // Time in minutes
    const words = typedChars / 5; // Assuming average word length of 5 characters
    const wpm = Math.round(words / elapsedTime);
    wpmDisplay.textContent = `WPM: ${wpm || 0}`; // Display WPM
  }
}

function updateAccuracy(): void {
  const accuracy = Math.round((correctChars / typedChars) * 100) || 0;
  accuracyDisplay.textContent = `Accuracy: ${accuracy}%`;
}

inputField.addEventListener('input', (event: Event) => {
  const input = (event.target as HTMLInputElement).value;
  const currentCharacter = textToType[currentIndex];

  if (startTime === null) {
    startTime = Date.now();
  }

  typedChars++;

  if (input[input.length - 1] === currentCharacter) {
    correctChars++;
    currentIndex++;
    (event.target as HTMLInputElement).value = ''; // Clear input
    updateTypingArea();
  } else {
    // Incorrect character
    (event.target as HTMLInputElement).value = input[input.length - 1]; // Keep the last character
  }

  calculateWPM();
  updateAccuracy();

  // Check for completion
  if (currentIndex === textToType.length) {
    alert('Congratulations! You finished!');
    const elapsedTime = (Date.now() - startTime!) / 1000;
    alert(`Time: ${elapsedTime.toFixed(2)} seconds`);
    startTime = null;
    typedChars = 0;
    correctChars = 0;
    currentIndex = 0;
    (event.target as HTMLInputElement).value = '';
    updateTypingArea();
    calculateWPM();
    updateAccuracy();
    inputField.focus();
  }
});

resetButton.addEventListener('click', () => {
    startTime = null;
    typedChars = 0;
    correctChars = 0;
    resetTyping();
    calculateWPM();
    updateAccuracy();
});

Here’s what changed:

  • We added `startTime`, `typedChars`, and `correctChars` variables.
  • We created two `div` elements to display WPM and accuracy and appended them to the body.
  • We added `calculateWPM()` function to calculate WPM, which is displayed in the HTML.
  • We added `updateAccuracy()` function to calculate the accuracy, which is also displayed in the HTML.
  • We updated the input event listener to track the metrics, and show the results to the user.
  • We reset these metrics in the `resetTyping` and `resetButton` functions.

7. Adding a Text Selection Feature

To enhance the typing tutor, we can provide the user with the ability to choose a text to practice with. This will make it more versatile and suitable for different practice needs. We will add a selection of texts to type, allowing the user to switch between them.

// index.ts (continued)

const textOptions = [
  "The quick brown fox jumps over the lazy dog.",
  "Pack my box with five dozen liquor jugs.",
  "How quickly daft jumping zebras vex."
];

let textToTypeIndex = 0;

function setTextToType() {
  textToType = textOptions[textToTypeIndex];
  currentIndex = 0;
  updateTypingArea();
}

// Add a dropdown to select the text
const textSelect = document.createElement('select');
textSelect.id = 'text-select';
textOptions.forEach((text, index) => {
  const option = document.createElement('option');
  option.value = index.toString();
  option.textContent = `Text ${index + 1}`;
  textSelect.appendChild(option);
});
document.body.insertBefore(textSelect, typingArea);

textSelect.addEventListener('change', () => {
  textToTypeIndex = parseInt(textSelect.value, 10);
  setTextToType();
  resetTyping();
});

setTextToType(); // Initialize with the first text

In this code:

  • We define an array `textOptions` containing several texts.
  • We create a `textToTypeIndex` variable to track the currently selected text.
  • The `setTextToType()` function updates the `textToType` based on the selected index and resets the typing area.
  • We add a select element to the HTML and populate it with options, one for each text in `textOptions`.
  • We add an event listener to the select element. When the user changes the selection, we update `textToTypeIndex`, call `setTextToType()` and reset the typing tutor.
  • The `setTextToType()` function is called initially to display the first text.

Compiling and Running the Code

Now that we’ve written the code, let’s compile it and run it. Open your terminal in your project directory and run the following command:

tsc

This command will compile your TypeScript code into JavaScript, creating a `index.js` file (or whatever you specified in `outDir`) in the `dist` directory (or wherever you specified in `outDir`).

To run the typing tutor, open `index.html` in your web browser. You should see the text to type, an input field, a reset button, and metrics for WPM and accuracy. Start typing, and the application should provide feedback and update the metrics accordingly.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to fix them when building a TypeScript application:

  • Incorrect Type Annotations: TypeScript relies on type annotations to catch errors early. If you make a mistake in your type annotations (e.g., assigning a string to a number variable), the compiler will throw an error. Always double-check your types.
  • Missing or Incorrect Imports: If you’re using external libraries or modules, make sure you import them correctly. The compiler will complain if it can’t find a module. Use the correct import syntax (e.g., `import { someFunction } from ‘./myModule’;`).
  • Event Handling Issues: When working with events, ensure you’re using the correct event types and that you handle the events properly. For example, when accessing the value of an input element, use `(event.target as HTMLInputElement).value`.
  • Incorrect DOM Manipulation: Be careful when manipulating the DOM (Document Object Model). Make sure you’re selecting the correct elements and updating their properties correctly. Use the correct type assertions when using `document.getElementById`.
  • Compiler Errors: Pay attention to the compiler errors. They provide valuable information about what’s wrong with your code. Read the error messages carefully and use them to guide your debugging process.

Key Takeaways

In this tutorial, you’ve learned how to:

  • Set up a TypeScript development environment.
  • Create a basic HTML structure and integrate it with your TypeScript code.
  • Use TypeScript to handle user input and provide feedback.
  • Implement dynamic updates to the user interface.
  • Add metrics to track typing speed and accuracy.
  • Add a reset function to improve the user experience.

You’ve also gained practical experience with essential TypeScript concepts such as variables, types, event handling, and DOM manipulation. This project provides a solid foundation for building more complex interactive applications with TypeScript.

FAQ

Here are some frequently asked questions about building a typing tutor with TypeScript:

  1. Can I customize the text to type? Yes, you can easily modify the `textToType` variable or add a feature to allow users to input their own text.
  2. How can I add more features? You can add features such as different difficulty levels, timers, sound effects, and more detailed statistics.
  3. Is it possible to use different fonts and themes? Yes, you can customize the CSS to change the fonts, colors, and overall appearance of the typing tutor.
  4. How can I deploy this application? You can deploy it as a static website on platforms like Netlify, GitHub Pages, or any other hosting service that supports HTML, CSS, and JavaScript.
  5. How can I improve performance? For very long texts or complex features, consider optimizing your code by using techniques like debouncing or throttling input events to reduce unnecessary updates.

Building a typing tutor is an excellent way to learn TypeScript and create something useful. By following this tutorial, you’ve gained practical experience with core TypeScript concepts and built a functional application. Remember, practice makes perfect. The more you code, the better you’ll become. Keep experimenting, exploring new features, and refining your skills. The world of TypeScript and web development is vast, and there’s always more to learn. Keep coding, keep creating, and enjoy the process!