TypeScript Tutorial: Building a Simple Interactive Web-Based Feedback Form

In the digital age, gathering feedback is crucial for improving products, services, and overall user experience. Imagine you’re building a website or application. How do you know if users are enjoying it? How can you identify areas that need improvement? A feedback form is the answer! It’s a simple, yet powerful tool that allows you to collect valuable insights directly from your audience. This tutorial will guide you through building a simple, interactive web-based feedback form using TypeScript, focusing on clarity, practicality, and ease of understanding.

Why TypeScript?

You might be wondering, “Why TypeScript?” TypeScript is a superset of JavaScript that adds static typing. This means it helps you catch errors early in the development process, improving code quality and making it easier to maintain and scale your projects. For beginners, it might seem like an extra layer of complexity, but the benefits – especially as your projects grow – are significant. TypeScript provides:

  • Improved Code Readability: Types make your code easier to understand.
  • Early Error Detection: Catching bugs during development saves time and frustration.
  • Enhanced Code Completion: IDEs can provide better suggestions and autocompletion.
  • Better Refactoring: TypeScript makes it safer to refactor your code.

Project Setup

Let’s get started! First, you’ll need Node.js and npm (Node Package Manager) installed on your system. These are essential for managing project dependencies and running your TypeScript code. Open your terminal or command prompt and create a new project directory:

mkdir feedback-form
cd feedback-form

Next, initialize a new npm project:

npm init -y

This command creates a package.json file, which will hold your project’s metadata and dependencies. Now, let’s install TypeScript and a few other packages we’ll need:

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

The --save-dev flag indicates that these packages are development dependencies, meaning they are only needed during development. We’ve also installed @types/dom-parser which is a type definition package for parsing DOM elements.

Configuring TypeScript

To configure TypeScript, we need a tsconfig.json file. This file tells the TypeScript compiler how to compile your code. Run the following command to generate a basic tsconfig.json:

npx tsc --init

This command creates a tsconfig.json file in your project directory. Open this file in your code editor and customize it. Here’s a basic configuration that works well for our project:

{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"]
}

Let’s break down some of these options:

  • target: Specifies the JavaScript version to compile to (es5 is widely supported).
  • module: Specifies the module system (commonjs is common for Node.js).
  • outDir: Specifies the output directory for the compiled JavaScript files.
  • rootDir: Specifies the root directory of your TypeScript files.
  • strict: Enables strict type checking.
  • esModuleInterop: Enables interoperability between CommonJS and ES modules.
  • skipLibCheck: Skips type checking of declaration files (improves build speed).
  • forceConsistentCasingInFileNames: Enforces consistent casing in file names.
  • include: Specifies which files to include in the compilation.

Project Structure

Create the following directory structure in your project:

feedback-form/
├── src/
│   ├── index.ts
├── dist/
├── package.json
├── tsconfig.json

The src directory will hold your TypeScript source files, and the dist directory will hold the compiled JavaScript files. We’ll put our main application logic in src/index.ts.

Coding the Feedback Form

Now, let’s write the TypeScript code for our feedback form. Open src/index.ts in your code editor. We’ll start by creating a simple HTML form:

// src/index.ts

const formHTML = `
  <div id="feedback-form-container" style="font-family: sans-serif; padding: 20px; border: 1px solid #ccc; border-radius: 5px; width: 400px; margin: 20px auto;">
    <h2>Feedback Form</h2>
    <form id="feedback-form">
      <label for="name">Name:</label><br>
      <input type="text" id="name" name="name" required><br><br>

      <label for="email">Email:</label><br>
      <input type="email" id="email" name="email" required><br><br>

      <label for="feedback">Feedback:</label><br>
      <textarea id="feedback" name="feedback" rows="4" cols="30" required></textarea><br><br>

      <button type="submit">Submit</button>
    </form>
    <div id="feedback-message" style="margin-top: 10px; color: green; display: none;">Thank you for your feedback!</div>
  </div>
`;

This HTML string defines a basic form with fields for name, email, and feedback. It also includes a success message that will be displayed after the form is submitted. Note that we’re using template literals (backticks) to create the HTML string, which makes it easier to read and maintain.

Now, let’s inject this HTML into the page. We’ll create a function to do this:


function injectForm() {
  const container = document.createElement('div');
  container.innerHTML = formHTML;
  document.body.appendChild(container);
}

This function creates a new `div` element, sets its `innerHTML` to our form HTML, and then appends it to the document’s body. Let’s call this function to add the form to the page:


injectForm();

Now, let’s add some event listeners to handle form submission. We’ll add an event listener to the form’s submit event:


function setupForm() {
  const form = document.getElementById('feedback-form') as HTMLFormElement;
  const message = document.getElementById('feedback-message') as HTMLDivElement;

  if (!form || !message) {
    console.error('Feedback form or message element not found.');
    return;
  }

  form.addEventListener('submit', (event: Event) => {
    event.preventDefault(); // Prevent the default form submission

    // Get form data
    const name = (document.getElementById('name') as HTMLInputElement).value;
    const email = (document.getElementById('email') as HTMLInputElement).value;
    const feedback = (document.getElementById('feedback') as HTMLTextAreaElement).value;

    // Basic validation
    if (!name || !email || !feedback) {
      alert('Please fill out all fields.');
      return;
    }

    // Simulate sending feedback (replace with actual API call)
    console.log('Feedback submitted:', { name, email, feedback });

    // Show success message
    message.style.display = 'block';

    // Clear the form
    form.reset();

    // Hide message after a few seconds
    setTimeout(() => {
      message.style.display = 'none';
    }, 3000);
  });
}

// Call setupForm after the form is injected
setupForm();

Here’s what this code does:

  • Gets form and message elements: Retrieves the form and success message elements from the DOM.
  • Adds submit event listener: Attaches an event listener to the form’s submit event.
  • Prevents default submission: Calls `event.preventDefault()` to prevent the default form submission behavior (page refresh).
  • Gets form data: Retrieves the values from the form fields.
  • Basic validation: Checks if all fields are filled out.
  • Simulates sending feedback: Logs the form data to the console (replace this with an API call to send the feedback to your server).
  • Shows success message: Displays the success message.
  • Clears the form: Resets the form fields.
  • Hides message after a delay: Hides the success message after 3 seconds.

To ensure our functions are executed after the page loads, we’ll wrap our code in an event listener for the `DOMContentLoaded` event:


document.addEventListener('DOMContentLoaded', () => {
  injectForm();
  setupForm();
});

This ensures that the form is injected and the event listeners are attached after the HTML is fully loaded and parsed. Now, let’s compile and run our code.

Compiling and Running the Code

Open your terminal and run the following command to compile your TypeScript code:

tsc

This will compile your src/index.ts file and create a dist/index.js file. Now, create an index.html file in the root of your project with the following content:

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

This HTML file includes the compiled JavaScript file (`dist/index.js`). Open index.html in your browser. You should see the feedback form. Fill it out and submit it. The feedback data will be logged in your browser’s console, and the success message will appear.

Adding Types for Better Code

Let’s make our code more robust by adding types. This is where TypeScript really shines. We’ll define types for our form data:


// Define a type for the form data
interface FeedbackData {
  name: string;
  email: string;
  feedback: string;
}

Now, let’s update our `setupForm` function to use this type:


function setupForm() {
  const form = document.getElementById('feedback-form') as HTMLFormElement;
  const message = document.getElementById('feedback-message') as HTMLDivElement;

  if (!form || !message) {
    console.error('Feedback form or message element not found.');
    return;
  }

  form.addEventListener('submit', (event: Event) => {
    event.preventDefault();

    // Get form data
    const name = (document.getElementById('name') as HTMLInputElement).value;
    const email = (document.getElementById('email') as HTMLInputElement).value;
    const feedback = (document.getElementById('feedback') as HTMLTextAreaElement).value;

    // Create an object of type FeedbackData
    const formData: FeedbackData = {
      name: name,
      email: email,
      feedback: feedback,
    };

    // Basic validation
    if (!formData.name || !formData.email || !formData.feedback) {
      alert('Please fill out all fields.');
      return;
    }

    // Simulate sending feedback (replace with actual API call)
    console.log('Feedback submitted:', formData);

    // Show success message
    message.style.display = 'block';

    // Clear the form
    form.reset();

    // Hide message after a few seconds
    setTimeout(() => {
      message.style.display = 'none';
    }, 3000);
  });
}

By using the `FeedbackData` type, we ensure that the form data always has the expected structure. If you accidentally try to assign a value with the wrong type, TypeScript will catch the error during compilation.

Error Handling

Robust error handling is essential for any application. In our current code, we have basic validation. Let’s expand on this. Consider these improvements:

  • Email validation: Check if the email address is in a valid format.
  • Server-side validation: The client-side validation is important, but you should also validate the data on the server-side to prevent malicious input.
  • Error messages: Display more informative error messages to the user.

Here’s how you can add email validation:


// Helper function for email validation
function isValidEmail(email: string): boolean {
  // Basic email validation regex
  const emailRegex = /^[w-.]+@([w-]+.)+[w-]{2,4}$/;
  return emailRegex.test(email);
}

Then, modify our submission handling to include email validation:


// Inside the submit event listener...

    const name = (document.getElementById('name') as HTMLInputElement).value;
    const email = (document.getElementById('email') as HTMLInputElement).value;
    const feedback = (document.getElementById('feedback') as HTMLTextAreaElement).value;

    // Create an object of type FeedbackData
    const formData: FeedbackData = {
      name: name,
      email: email,
      feedback: feedback,
    };

    // Basic validation
    if (!formData.name || !formData.email || !formData.feedback) {
      alert('Please fill out all fields.');
      return;
    }

    if (!isValidEmail(formData.email)) {
        alert('Please enter a valid email address.');
        return;
    }

This will validate the email address before submitting the form. If it’s not a valid format, it will show an alert message. Remember to replace the `alert` calls with more user-friendly error display methods (e.g., displaying error messages next to the input fields).

Styling the Form

The form currently looks quite plain. Let’s add some basic styling to make it more visually appealing. You can add CSS styles directly in the HTML (inline styles), in a <style> tag in the <head> of your HTML, or in a separate CSS file. For this example, let’s use inline styles within the form HTML string.


const formHTML = `
  <div id="feedback-form-container" style="font-family: sans-serif; padding: 20px; border: 1px solid #ccc; border-radius: 5px; width: 400px; margin: 20px auto;">
    <h2 style="text-align: center;">Feedback Form</h2>
    <form id="feedback-form" style="display: flex; flex-direction: column;">
      <label for="name" style="margin-bottom: 5px;">Name:</label>
      <input type="text" id="name" name="name" required style="padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px;">

      <label for="email" style="margin-bottom: 5px;">Email:</label>
      <input type="email" id="email" name="email" required style="padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px;">

      <label for="feedback" style="margin-bottom: 5px;">Feedback:</label>
      <textarea id="feedback" name="feedback" rows="4" cols="30" required style="padding: 8px; margin-bottom: 10px; border: 1px solid #ddd; border-radius: 4px; resize: vertical;"></textarea>

      <button type="submit" style="padding: 10px 15px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer;">Submit</button>
    </form>
    <div id="feedback-message" style="margin-top: 10px; color: green; display: none; text-align: center;">Thank you for your feedback!</div>
  </div>
`;

This adds some basic styling, including:

  • Setting a font family.
  • Adding padding and a border to the container.
  • Styling the form elements (labels, inputs, textarea, button).
  • Adding a success message.

For more complex styling, consider using a separate CSS file. Create a file named style.css in your project directory (e.g., in a src folder) and link it to your HTML file using a <link> tag in the <head>.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Feedback Form</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <script src="dist/index.js"></script>
</body>
</html>

Then, in your style.css file, you can add your styles:


#feedback-form-container {
  font-family: sans-serif;
  padding: 20px;
  border: 1px solid #ccc;
  border-radius: 5px;
  width: 400px;
  margin: 20px auto;
}

h2 {
  text-align: center;
}

#feedback-form {
  display: flex;
  flex-direction: column;
}

label {
  margin-bottom: 5px;
}

input[type="text"], input[type="email"], textarea {
  padding: 8px;
  margin-bottom: 10px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

textarea {
  resize: vertical;
}

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

#feedback-message {
  margin-top: 10px;
  color: green;
  text-align: center;
}

Making an API Call

The current implementation only logs the feedback to the console. In a real-world application, you’ll want to send this data to a server. This typically involves making an API call. For this example, we’ll simulate an API call using the fetch API. First, let’s create a function to handle the API call:


async function sendFeedback(data: FeedbackData): Promise<boolean> {
  try {
    const response = await fetch('/api/feedback', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    });

    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }

    const result = await response.json();
    console.log('Success:', result);
    return true;

  } catch (error) {
    console.error('Error sending feedback:', error);
    return false;
  }
}

This function does the following:

  • Uses the `fetch` API: Makes a POST request to the `/api/feedback` endpoint.
  • Sets headers: Sets the `Content-Type` header to `application/json`.
  • Sends data: Stringifies the `data` object to JSON and sends it in the request body.
  • Handles response: Checks if the response is successful (status code 200-299). If not, it throws an error.
  • Parses JSON response: Parses the response as JSON.
  • Error handling: Catches any errors during the process and logs them to the console.

Now, let’s update our `setupForm` function to use this `sendFeedback` function:


async function setupForm() {
  const form = document.getElementById('feedback-form') as HTMLFormElement;
  const message = document.getElementById('feedback-message') as HTMLDivElement;

  if (!form || !message) {
    console.error('Feedback form or message element not found.');
    return;
  }

  form.addEventListener('submit', async (event: Event) => {
    event.preventDefault();

    const name = (document.getElementById('name') as HTMLInputElement).value;
    const email = (document.getElementById('email') as HTMLInputElement).value;
    const feedback = (document.getElementById('feedback') as HTMLTextAreaElement).value;

    const formData: FeedbackData = {
      name: name,
      email: email,
      feedback: feedback,
    };

    if (!formData.name || !formData.email || !formData.feedback) {
      alert('Please fill out all fields.');
      return;
    }

    if (!isValidEmail(formData.email)) {
        alert('Please enter a valid email address.');
        return;
    }

    const success = await sendFeedback(formData);

    if (success) {
      message.style.display = 'block';
      form.reset();
      setTimeout(() => {
        message.style.display = 'none';
      }, 3000);
    } else {
      alert('There was an error submitting your feedback. Please try again.');
    }
  });
}

In this updated code:

  • We’ve made the `submit` event listener `async` to allow for the use of `await`.
  • We call the `sendFeedback` function and await its result.
  • Based on the result (success or failure), we display the success message or an error message.

To test this, you’ll need to set up a backend server to handle the `/api/feedback` endpoint. This is beyond the scope of this tutorial, but you can use Node.js with Express, Python with Flask or Django, or any other backend framework you prefer. The server would receive the POST request, process the data (e.g., save it to a database), and then send back a response.

Common Mistakes and How to Fix Them

Here are some common mistakes beginners make when working with TypeScript and web forms, along with solutions:

  • Incorrect Type Annotations: Forgetting to add types or using incorrect types is a frequent error. TypeScript will catch these during compilation. Always double-check your type annotations. Use the correct HTML element types (e.g., `HTMLInputElement`, `HTMLTextAreaElement`).
  • DOM Element Selection Issues: Make sure you’re selecting the correct DOM elements using `document.getElementById()`. If an element is not found, the code will throw an error or not work as expected. Always check for null values after selecting an element. Use type assertions (e.g., `as HTMLInputElement`) to help TypeScript understand the element’s type.
  • Event Listener Context: When working with event listeners, be mindful of the `this` context. If you’re using class methods as event listeners, you might need to bind the method to the correct `this` context using `.bind(this)` or use arrow functions.
  • Asynchronous Operations: When working with asynchronous operations (e.g., `fetch` API calls), make sure to handle the responses correctly using `async/await` or `.then()` and `.catch()`. Properly handle errors that may occur during the API call.
  • CORS (Cross-Origin Resource Sharing) Errors: If you’re making API calls to a different domain, you might encounter CORS errors. The server needs to be configured to allow requests from your domain. This usually involves setting the `Access-Control-Allow-Origin` header in the server’s response.
  • Not Using `event.preventDefault()`: When handling form submissions, don’t forget to call `event.preventDefault()` to prevent the default form submission behavior, which can cause a page refresh.

Key Takeaways

In this tutorial, we’ve covered the fundamentals of building a simple, interactive web-based feedback form using TypeScript. You’ve learned how to:

  • Set up a TypeScript project.
  • Create HTML forms dynamically.
  • Handle form submissions.
  • Add basic validation.
  • Style your form using CSS.
  • Make API calls.
  • Use types to improve code quality.

Remember that this is a basic example. You can extend this further by adding more features like:

  • More sophisticated validation.
  • Client-side and server-side validation.
  • Storing the feedback data in a database.
  • Implementing CAPTCHA or other spam prevention measures.
  • Adding a progress indicator during API calls.
  • Using a framework like React, Angular, or Vue.js for a more complex UI.

FAQ

Here are some frequently asked questions about building feedback forms with TypeScript:

  1. Can I use this form on any website? Yes, the form can be adapted for any website. You’ll need to adjust the HTML, styling, and API endpoint to match your specific needs.
  2. How do I handle the feedback data? You’ll need a backend server to receive and process the feedback data. This could involve saving the data to a database, sending email notifications, or performing other actions.
  3. What if I don’t want to use TypeScript? You can build the form using plain JavaScript. However, TypeScript offers significant benefits in terms of code quality, maintainability, and error detection.
  4. How can I make the form accessible? Ensure your form is accessible by using semantic HTML, providing labels for all form fields, and using appropriate ARIA attributes. Test your form with a screen reader to ensure it is usable for people with disabilities.
  5. Can I integrate this form with a third-party service? Yes, you can integrate this form with third-party services like email marketing platforms, CRM systems, or analytics tools. You’ll typically do this by making API calls to these services from your backend server.

This tutorial provides a solid foundation for creating interactive web forms. As you continue to build projects and gain experience, you’ll discover new ways to improve your code and create even more engaging user experiences. The power of TypeScript, coupled with a well-designed form, empowers you to gather valuable insights and build better products. Keep experimenting, keep learning, and don’t be afraid to try new things. The world of web development is constantly evolving, and the journey of learning is a rewarding one.