TypeScript Tutorial: Building a Simple Interactive Survey App

Surveys are everywhere. From gathering customer feedback on a new product to understanding employee satisfaction, they’re a crucial tool for collecting data and making informed decisions. But building a survey app can seem daunting. Where do you start? How do you handle different question types? How do you store and analyze the responses?

This tutorial will guide you through building a simple, interactive survey application using TypeScript. We’ll focus on creating a user-friendly interface, handling various question formats, and storing user responses. By the end, you’ll have a solid understanding of how to use TypeScript to create dynamic web applications and a working survey app you can adapt and expand upon.

Why TypeScript for a Survey App?

TypeScript offers several advantages for this project:

  • Type Safety: TypeScript helps catch errors early in the development process. You define the types of your variables, function parameters, and return values. This prevents common mistakes like passing the wrong data type to a function or accessing properties that don’t exist.
  • Code Completion and Refactoring: TypeScript provides excellent code completion and refactoring support in most IDEs. This makes writing and maintaining your code much easier and faster.
  • Object-Oriented Programming (OOP): TypeScript supports OOP principles like classes, inheritance, and polymorphism. This allows you to organize your code logically and create reusable components.
  • Scalability: As your survey app grows, TypeScript’s type system and OOP features will help you manage the complexity of the codebase.

Setting Up Your Project

Before we dive into the code, let’s set up our project. You’ll need Node.js and npm (Node Package Manager) installed on your system. If you don’t have them, download and install them from the official Node.js website.

Create a new project directory and navigate into it using your terminal:

mkdir survey-app
cd survey-app

Initialize a new npm project:

npm init -y

This will create a `package.json` file in your project directory. Now, install TypeScript and the necessary types for DOM manipulation:

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

Create a `tsconfig.json` file to configure the TypeScript compiler. In your project directory, run:

npx tsc --init

This will generate a `tsconfig.json` file with default configurations. You can customize this file to suit your project’s needs. For now, we’ll keep the default settings, but you might want to modify the `outDir` option to specify where the compiled JavaScript files will be placed.

Finally, create a directory called `src` where we’ll put our TypeScript code. Inside `src`, create a file named `index.ts`. This will be the entry point of our application.

Building the Survey App: Core Components

Let’s start building the core components of our survey app. We’ll define the data structures for questions and surveys and create the basic HTML structure.

Defining Question Types

We’ll support three question types:

  • Text Input: For short answers.
  • Multiple Choice: With single selection.
  • Checkboxes: With multiple selections allowed.

Let’s define a TypeScript interface for a question:

interface Question {
    type: 'text' | 'multipleChoice' | 'checkbox';
    text: string;
    options?: string[]; // Only for multipleChoice and checkbox
    id: string; // Unique identifier for each question
}

This interface defines the structure of a question. The `type` property specifies the question type, `text` is the question itself, `options` is an array of options for multiple-choice and checkbox questions, and `id` is a unique identifier. The `id` is crucial for linking questions to their respective answers.

Defining the Survey Interface

Now, let’s define an interface for the survey itself:

interface Survey {
    title: string;
    description: string;
    questions: Question[];
}

This interface defines the structure of a survey. It has a `title`, a `description`, and an array of `questions` (which are defined by the `Question` interface). This allows us to group multiple questions into a cohesive survey.

Creating the HTML Structure

Let’s create a basic HTML structure to display the survey. Create an `index.html` file in your project directory:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Survey App</title>
</head>
<body>
    <div id="survey-container"></div>
    <script src="./dist/index.js"></script>
</body>
</html>

This HTML sets up the basic structure of the page, including a container div with the ID “survey-container” where we’ll render the survey. It also includes a script tag that points to the compiled JavaScript file, which we’ll generate from our TypeScript code.

Implementing the Survey Logic

Now, let’s write the TypeScript code to handle the survey logic. Open `src/index.ts` and start by importing the necessary types and creating a sample survey:

// src/index.ts

interface Question {
    type: 'text' | 'multipleChoice' | 'checkbox';
    text: string;
    options?: string[];
    id: string;
}

interface Survey {
    title: string;
    description: string;
    questions: Question[];
}

// Sample survey data
const survey: Survey = {
    title: "Customer Satisfaction Survey",
    description: "Help us improve our services!",
    questions: [
        {
            type: 'text',
            text: 'What is your name?',
            id: 'name'
        },
        {
            type: 'multipleChoice',
            text: 'How satisfied are you with our product?',
            options: ['Very Satisfied', 'Satisfied', 'Neutral', 'Dissatisfied', 'Very Dissatisfied'],
            id: 'satisfaction'
        },
        {
            type: 'checkbox',
            text: 'What features do you use?',
            options: ['Feature A', 'Feature B', 'Feature C'],
            id: 'features'
        }
    ]
};

This code defines the `Question` and `Survey` interfaces and creates a sample survey with three questions of different types. This sample data will be used to render the survey in the HTML.

Rendering the Survey

Next, let’s write a function to render the survey in the HTML:

// src/index.ts
// ... (previous code)

function renderSurvey(survey: Survey): void {
    const container = document.getElementById('survey-container');
    if (!container) {
        console.error('Survey container not found.');
        return;
    }

    let html = `<h2>${survey.title}</h2>`;
    html += `<p>${survey.description}</p>`;

    survey.questions.forEach(question => {
        html += renderQuestion(question);
    });

    html += '<button id="submit-button">Submit</button>';

    container.innerHTML = html;

    const submitButton = document.getElementById('submit-button');
    if (submitButton) {
        submitButton.addEventListener('click', handleSubmit);
    }
}

This `renderSurvey` function takes a `Survey` object as input and dynamically generates the HTML for the survey. It retrieves the survey container from the DOM, iterates through the questions, and calls the `renderQuestion` function (which we’ll define next) to render each question. It also adds a submit button and attaches an event listener to it. The function checks for the existence of the container and handles potential errors gracefully.

Rendering Individual Questions

Now, let’s define the `renderQuestion` function:

// src/index.ts
// ... (previous code)

function renderQuestion(question: Question): string {
    let html = `<div class="question" id="question-${question.id}">`;
    html += `<p>${question.text}</p>`;

    switch (question.type) {
        case 'text':
            html += `<input type="text" id="${question.id}">`;
            break;
        case 'multipleChoice':
            question.options?.forEach((option, index) => {
                html += `<label><input type="radio" name="${question.id}" value="${option}"> ${option}</label><br>`;
            });
            break;
        case 'checkbox':
            question.options?.forEach((option, index) => {
                html += `<label><input type="checkbox" name="${question.id}" value="${option}"> ${option}</label><br>`;
            });
            break;
    }

    html += '</div>';
    return html;
}

The `renderQuestion` function takes a `Question` object and generates the HTML for that specific question. It uses a switch statement to handle different question types, generating the appropriate HTML input elements (text input, radio buttons, or checkboxes). It also adds labels for each option in the multiple-choice and checkbox questions.

Handling the Submit Button

Let’s implement the `handleSubmit` function to handle the submission of the survey responses:

// src/index.ts
// ... (previous code)

function handleSubmit(): void {
    const answers: { [key: string]: string | string[] } = {};

    survey.questions.forEach(question => {
        switch (question.type) {
            case 'text':
                const textInput = document.getElementById(question.id) as HTMLInputElement;
                answers[question.id] = textInput?.value || '';
                break;
            case 'multipleChoice':
                const radioButtons = document.getElementsByName(question.id) as NodeListOf<HTMLInputElement>;
                for (let i = 0; i < radioButtons.length; i++) {
                    if (radioButtons[i].checked) {
                        answers[question.id] = radioButtons[i].value;
                        break;
                    }
                }
                break;
            case 'checkbox':
                const checkboxes = document.getElementsByName(question.id) as NodeListOf<HTMLInputElement>;
                const checkedValues: string[] = [];
                for (let i = 0; i < checkboxes.length; i++) {
                    if (checkboxes[i].checked) {
                        checkedValues.push(checkboxes[i].value);
                    }
                }
                answers[question.id] = checkedValues;
                break;
        }
    });

    console.log(answers);
    alert('Thank you for your feedback!');
}

This `handleSubmit` function is called when the submit button is clicked. It iterates through the questions and collects the user’s answers. It uses different logic based on the question type to retrieve the selected values from the input elements. Finally, it logs the answers to the console and displays an alert to the user. This function retrieves the values from the input elements and stores them in an `answers` object, which is then logged to the console. The function handles the different question types (text, multipleChoice, and checkbox) accordingly.

Putting it all together

Finally, call the `renderSurvey` function after defining all the functions and interfaces:

// src/index.ts
// ... (previous code)

// Render the survey
renderSurvey(survey);

This line calls the `renderSurvey` function, passing in the `survey` object. This initiates the rendering process, displaying the survey in the HTML.

Compiling and Running the Application

Now that we’ve written our TypeScript code, let’s compile it and run the application.

Open your terminal and navigate to your project directory. Run the following command to compile the TypeScript code into JavaScript:

tsc

This command will use the `tsconfig.json` configuration to compile all TypeScript files in the `src` directory into JavaScript files in the `dist` directory (or the directory specified in your `tsconfig.json`).

Open `index.html` in your web browser. You should see the survey rendered. Fill out the survey and click the submit button. The answers will be logged to the browser’s console.

Styling the Survey (Optional)

To make the survey visually appealing, you can add some CSS styling. Create a file named `style.css` in your project directory and link it to your `index.html` file within the `<head>` tags:

<link rel="stylesheet" href="style.css">

Here’s some example CSS you can use to get started:

/* style.css */
body {
    font-family: sans-serif;
    margin: 20px;
}

.question {
    margin-bottom: 15px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="radio"], input[type="checkbox"] {
    margin-right: 5px;
}

button {
    padding: 10px 20px;
    background-color: #4CAF50;
    color: white;
    border: none;
    cursor: pointer;
}

This CSS provides basic styling for the survey elements. You can customize the styles to match your design preferences.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Incorrect TypeScript Setup: Ensure your `tsconfig.json` is correctly configured and that you have installed the necessary types (e.g., `@types/dom`). Double-check your import statements and make sure you’re importing types correctly.
  • Type Errors: TypeScript’s type system can be strict. Pay attention to type errors in your IDE and fix them by ensuring your variables and function parameters have the correct types.
  • Incorrect DOM Element Selection: When selecting DOM elements, make sure the element exists before attempting to access its properties. Use null checks and type assertions (e.g., `as HTMLInputElement`) to avoid errors.
  • Event Listener Issues: Ensure event listeners are correctly attached to the elements. Verify that the event is being triggered by testing with `console.log()` statements within the event handler.

Enhancements and Next Steps

This tutorial provides a basic framework for a survey app. Here are some ways to enhance it:

  • Data Persistence: Implement a mechanism to store the survey responses. This could involve using local storage, a database, or sending the data to a server.
  • More Question Types: Add support for more question types, such as dropdown menus, text areas, and rating scales.
  • Validation: Implement client-side validation to ensure users provide valid input.
  • Error Handling: Add more robust error handling to handle different scenarios, such as network errors or invalid data.
  • User Interface (UI) Improvements: Enhance the UI with CSS and potentially a UI framework to make the survey more visually appealing and user-friendly.
  • Accessibility: Ensure the survey is accessible to users with disabilities by using semantic HTML and ARIA attributes.

Key Takeaways

This tutorial covered the core concepts of building a simple, interactive survey app using TypeScript. We’ve seen how to define data structures, render dynamic HTML, handle user input, and manage different question types. This foundational knowledge provides a solid base for creating more complex web applications and understanding the benefits of using TypeScript in your projects. Remember to practice regularly, experiment with different features, and embrace the type safety and code organization that TypeScript offers.

By following this tutorial, you’ve gained practical experience in using TypeScript to create a functional web application. You’ve learned how to structure your code, handle user interactions, and work with different data types. This project is a starting point. Feel free to experiment with the code, add new features, and explore other possibilities. The skills you’ve acquired will be invaluable as you continue your journey in web development. Keep learning, keep building, and always strive to improve your skills. The world of web development is constantly evolving, so embrace the challenges and enjoy the process of creating!