TypeScript Tutorial: Building a Simple Interactive E-commerce Shopping Cart

In the world of web development, creating an intuitive and responsive e-commerce shopping cart is a fundamental skill. From small online boutiques to large-scale marketplaces, the shopping cart is the backbone of any e-commerce platform. This tutorial will guide you through building a simple, yet functional, interactive shopping cart using TypeScript. We’ll focus on the core functionalities: adding products, updating quantities, calculating totals, and displaying the cart’s contents. This project will not only solidify your understanding of TypeScript but also provide you with practical experience in handling user interactions and managing data within a web application.

Why TypeScript?

TypeScript, a superset of JavaScript, brings static typing to JavaScript. This means that you can define the types of variables, function parameters, and return values. This provides several benefits:

  • Early Error Detection: TypeScript catches type-related errors during development, before runtime. This saves you time and frustration by preventing bugs.
  • Improved Code Readability: Type annotations make your code easier to understand and maintain, especially in large projects.
  • Enhanced Code Completion: IDEs can provide better code completion and suggestions, thanks to the type information.
  • Refactoring Safety: TypeScript makes refactoring safer by helping you identify and fix potential issues when you change your code.

By using TypeScript, we can build a more robust and maintainable shopping cart application.

Project Setup

Before we start coding, let’s set up our project. We’ll use npm (Node Package Manager) to manage our dependencies and TypeScript to compile our code.

  1. Create a Project Directory: Create a new directory for your project, for example, `shopping-cart-typescript`.
  2. Initialize npm: Open your terminal, navigate to your project directory, and run `npm init -y`. This will create a `package.json` file.
  3. Install TypeScript: Install TypeScript as a development dependency by running `npm install –save-dev typescript`.
  4. Initialize TypeScript: Create a `tsconfig.json` file by running `npx tsc –init`. This file configures the TypeScript compiler. You can customize the settings in this file to suit your project’s needs. For a basic setup, you can keep the default settings.
  5. Create Source Files: Create a directory called `src` where we’ll put our TypeScript files. Inside `src`, create a file named `cart.ts`.

Your project directory structure should look like this:

shopping-cart-typescript/
├── node_modules/
├── package.json
├── tsconfig.json
└── src/
    └── cart.ts

Defining Data Structures

Let’s define the data structures we’ll use to represent products and the shopping cart items. We’ll use TypeScript interfaces for this purpose.

Open `src/cart.ts` and add the following code:


// Define an interface for a product
interface Product {
  id: number;
  name: string;
  price: number;
}

// Define an interface for a cart item
interface CartItem {
  product: Product;
  quantity: number;
}

In this code:

  • `Product` interface: Defines the structure of a product, including its `id`, `name`, and `price`.
  • `CartItem` interface: Defines the structure of an item in the cart, including the `product` and its `quantity`.

Implementing the Shopping Cart Class

Now, let’s create a `ShoppingCart` class that will handle the core logic of our shopping cart.

Add the following code to `src/cart.ts`:


// Define an interface for a product
interface Product {
  id: number;
  name: string;
  price: number;
}

// Define an interface for a cart item
interface CartItem {
  product: Product;
  quantity: number;
}

class ShoppingCart {
  private items: CartItem[] = [];

  // Add an item to the cart
  addItem(product: Product, quantity: number): void {
    const existingItemIndex = this.items.findIndex(item => item.product.id === product.id);

    if (existingItemIndex !== -1) {
      // If the item already exists, update the quantity
      this.items[existingItemIndex].quantity += quantity;
    } else {
      // If the item doesn't exist, add it to the cart
      this.items.push({ product, quantity });
    }
  }

  // Update the quantity of an item in the cart
  updateQuantity(productId: number, quantity: number): void {
    const itemIndex = this.items.findIndex(item => item.product.id === productId);

    if (itemIndex !== -1) {
      this.items[itemIndex].quantity = quantity;
      // Remove the item if the quantity is zero or less
      if (quantity  item.product.id !== productId);
  }

  // Get the items in the cart
  getItems(): CartItem[] {
    return this.items;
  }

  // Calculate the total price of the cart
  getTotal(): number {
    return this.items.reduce((total, item) => total + item.product.price * item.quantity, 0);
  }

  // Get the number of items in the cart
  getItemCount(): number {
    return this.items.reduce((count, item) => count + item.quantity, 0);
  }

  // Clear the cart
  clearCart(): void {
    this.items = [];
  }
}

Let’s break down the code:

  • `private items: CartItem[] = [];`: This line declares a private array called `items` to store the cart items. It is initialized as an empty array of `CartItem` objects. The `private` keyword ensures that this property can only be accessed from within the `ShoppingCart` class.
  • `addItem(product: Product, quantity: number): void`: This method adds a product to the cart. It takes a `product` of type `Product` and a `quantity` of type `number` as arguments.
  • updateQuantity(productId: number, quantity: number): void: This method updates the quantity of an existing item in the cart. It takes the `productId` and the new `quantity` as arguments. If the quantity is zero or less, the item is removed from the cart.
  • removeItem(productId: number): void: This method removes an item from the cart based on its `productId`. It uses the `filter` method to create a new array with the item removed.
  • getItems(): CartItem[]: This method returns the current items in the cart.
  • getTotal(): number: This method calculates the total price of all items in the cart. It uses the `reduce` method to iterate over the `items` array and sum the prices.
  • getItemCount(): number: This method returns the total number of items in the cart.
  • clearCart(): void: This method clears all items from the cart by setting the `items` array to an empty array.

Implementing the Shopping Cart in HTML and JavaScript

Now that we have the core logic of our shopping cart, let’s create a simple HTML page to interact with it. We’ll also use JavaScript to handle user interactions and update the cart’s display.

Create an `index.html` file in the root directory of your project and add the following code:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Shopping Cart</title>
  <style>
    body {
      font-family: sans-serif;
    }
    .product {
      border: 1px solid #ccc;
      padding: 10px;
      margin-bottom: 10px;
    }
    .cart-item {
      display: flex;
      justify-content: space-between;
      padding: 5px 0;
    }
  </style>
</head>
<body>
  <h2>Products</h2>
  <div id="products">
    <!-- Products will be displayed here -->
  </div>

  <h2>Shopping Cart</h2>
  <div id="cart">
    <!-- Cart items will be displayed here -->
  </div>
  <p>Total: <span id="total">0.00</span></p>
  <button id="clear-cart">Clear Cart</button>
  <script src="./dist/cart.js"></script>
</body>
</html>

Let’s break down the HTML:

  • The HTML includes basic structure with a title and some CSS for styling.
  • There are two main sections: `Products` and `Shopping Cart`.
  • The `Products` section will display available products.
  • The `Shopping Cart` section will display the items added to the cart.
  • The `Total` section will display the total cost of all items in the cart.
  • A `Clear Cart` button is included to clear the cart.
  • A script tag links to `cart.js` (which will be generated by the TypeScript compiler).

Now, let’s add the JavaScript code to `src/cart.ts` to interact with the HTML elements, and to initialize the shopping cart. Replace the existing content of `src/cart.ts` with the following code:


// Define an interface for a product
interface Product {
  id: number;
  name: string;
  price: number;
}

// Define an interface for a cart item
interface CartItem {
  product: Product;
  quantity: number;
}

class ShoppingCart {
  private items: CartItem[] = [];

  // Add an item to the cart
  addItem(product: Product, quantity: number): void {
    const existingItemIndex = this.items.findIndex(item => item.product.id === product.id);

    if (existingItemIndex !== -1) {
      // If the item already exists, update the quantity
      this.items[existingItemIndex].quantity += quantity;
    } else {
      // If the item doesn't exist, add it to the cart
      this.items.push({ product, quantity });
    }
    this.renderCart();
  }

  // Update the quantity of an item in the cart
  updateQuantity(productId: number, quantity: number): void {
    const itemIndex = this.items.findIndex(item => item.product.id === productId);

    if (itemIndex !== -1) {
      this.items[itemIndex].quantity = quantity;
      // Remove the item if the quantity is zero or less
      if (quantity  item.product.id !== productId);
    this.renderCart();
  }

  // Get the items in the cart
  getItems(): CartItem[] {
    return this.items;
  }

  // Calculate the total price of the cart
  getTotal(): number {
    return this.items.reduce((total, item) => total + item.product.price * item.quantity, 0);
  }

  // Get the number of items in the cart
  getItemCount(): number {
    return this.items.reduce((count, item) => count + item.quantity, 0);
  }

  // Clear the cart
  clearCart(): void {
    this.items = [];
    this.renderCart();
  }

  // Render the cart contents in the HTML
  renderCart(): void {
    const cartElement = document.getElementById('cart');
    const totalElement = document.getElementById('total');

    if (!cartElement || !totalElement) return;

    cartElement.innerHTML = ''; // Clear the cart

    this.getItems().forEach(item => {
      const cartItemElement = document.createElement('div');
      cartItemElement.classList.add('cart-item');
      cartItemElement.innerHTML = `
        <span>${item.product.name} - $${item.product.price.toFixed(2)} x ${item.quantity}</span>
        <button data-product-id="${item.product.id}" data-action="remove">Remove</button>
        <input type="number" data-product-id="${item.product.id}" data-action="update" value="${item.quantity}" min="0" style="width: 40px;">
      `;
      cartElement.appendChild(cartItemElement);
    });

    totalElement.textContent = this.getTotal().toFixed(2);

    // Add event listeners for remove buttons
    document.querySelectorAll('#cart button[data-action="remove"]').forEach(button => {
      button.addEventListener('click', (event) => {
        const productId = Number((event.target as HTMLButtonElement).dataset.productId);
        this.removeItem(productId);
      });
    });

    // Add event listeners for quantity inputs
    document.querySelectorAll('#cart input[data-action="update"]').forEach(input => {
      input.addEventListener('change', (event) => {
        const productId = Number((event.target as HTMLInputElement).dataset.productId);
        const quantity = Number((event.target as HTMLInputElement).value);
        this.updateQuantity(productId, quantity);
      });
    });
  }
}

// Sample Products
const products: Product[] = [
  { id: 1, name: 'T-Shirt', price: 25 },
  { id: 2, name: 'Jeans', price: 50 },
  { id: 3, name: 'Shoes', price: 75 },
];

const shoppingCart = new ShoppingCart();

// Function to render products
function renderProducts(): void {
  const productsElement = document.getElementById('products');
  if (!productsElement) return;

  products.forEach(product => {
    const productElement = document.createElement('div');
    productElement.classList.add('product');
    productElement.innerHTML = `
      <h3>${product.name} - $${product.price.toFixed(2)}</h3>
      <button data-product-id="${product.id}" data-action="add">Add to Cart</button>
    `;
    productsElement.appendChild(productElement);
  });

  // Add event listeners for add to cart buttons
  document.querySelectorAll('#products button[data-action="add"]').forEach(button => {
    button.addEventListener('click', (event) => {
      const productId = Number((event.target as HTMLButtonElement).dataset.productId);
      const product = products.find(p => p.id === productId);
      if (product) {
        shoppingCart.addItem(product, 1);
      }
    });
  });
}

// Add clear cart functionality
const clearCartButton = document.getElementById('clear-cart');
if (clearCartButton) {
  clearCartButton.addEventListener('click', () => {
    shoppingCart.clearCart();
  });
}

// Initial render
renderProducts();
shoppingCart.renderCart();

Let’s break down the updated code:

  • The `renderCart()` method is crucial for updating the cart display in the HTML. It clears the existing content of the cart, iterates through the items in the cart, and dynamically creates HTML elements for each item. It also adds event listeners to the remove buttons and quantity input fields to handle user interactions.
  • The `renderProducts()` method renders the list of products on the page. It iterates through the `products` array and creates HTML elements for each product. It also adds event listeners to the “Add to Cart” buttons.
  • Event listeners are added to the “Add to Cart” buttons, the “Remove” buttons and the quantity input fields.
  • Sample products are defined and rendered on the page.
  • A clear cart button has been added.

Compiling and Running the Application

Now, let’s compile our TypeScript code and run the application.

  1. Compile TypeScript: Open your terminal and run `npx tsc` in your project directory. This will compile the TypeScript code in `src/cart.ts` and generate a `cart.js` file in a `dist` directory. If you haven’t already, make sure the `outDir` in your `tsconfig.json` is set to `dist`.
  2. Open in Browser: Open `index.html` in your web browser. You should see the products and an empty shopping cart.
  3. Interact with the Cart: Click the “Add to Cart” buttons to add products to the cart. You should see the items appear in the cart. You can also change the quantity and remove items. The total price should update automatically.

Common Mistakes and How to Fix Them

Here are some common mistakes and how to avoid them:

  • Type Errors: TypeScript will highlight type errors during development. Make sure you fix these errors before running your code. Read the error messages carefully as they will guide you to where the problem lies.
  • Incorrect DOM Manipulation: When working with the DOM, make sure you select the correct elements and update them correctly. Use the browser’s developer tools to inspect the elements and check for any errors in the console.
  • Event Listener Issues: Ensure that event listeners are attached to the correct elements and that the event handlers are correctly implemented. Check the console for any errors related to event handling.
  • Incorrect Path to JavaScript file: Check the script tag in your `index.html` to ensure the path to the compiled JavaScript file is correct (e.g., “).
  • Ignoring TypeScript Errors: Don’t ignore TypeScript errors. They are there to help you catch bugs early. Resolve them before running your application.

Enhancements and Next Steps

This is a basic shopping cart implementation. You can extend it with the following features:

  • Local Storage: Save the cart items to local storage so that the cart persists even when the user closes the browser.
  • Product Images: Display product images.
  • More Complex Product Data: Add more product details like descriptions, sizes, and colors.
  • User Interface Improvements: Improve the user interface with better styling and layout.
  • Error Handling: Implement error handling to handle cases like invalid input or network errors.
  • Server-Side Integration: Integrate with a backend server to store product data and handle orders.
  • Payment Gateway Integration: Integrate with a payment gateway to process payments.

Key Takeaways

  • TypeScript enhances code quality and maintainability.
  • Interfaces define the structure of your data.
  • Classes encapsulate the logic of your application.
  • DOM manipulation allows you to interact with the HTML.
  • Event listeners enable user interaction.
  • Compiling with `tsc` transforms TypeScript into JavaScript.

FAQ

  1. Why use TypeScript instead of JavaScript?

    TypeScript adds static typing to JavaScript, which helps catch errors during development, improves code readability, and makes refactoring safer. This leads to more robust and maintainable code.

  2. How do I handle errors in TypeScript?

    TypeScript helps you catch errors at compile time. You can also use `try…catch` blocks for runtime error handling. Make sure you check the browser’s console for any errors that may occur.

  3. How can I debug my TypeScript code?

    You can debug your TypeScript code using your browser’s developer tools. You can also use a debugger in your IDE, such as Visual Studio Code. Set breakpoints in your code and step through it to identify the cause of any issues.

  4. How do I deploy this application?

    You can deploy this application by uploading the HTML, CSS, and JavaScript files to a web server. You can also use a platform like GitHub Pages, Netlify, or Vercel to host your application.

Building a shopping cart with TypeScript is a great way to learn and practice web development skills. By understanding the fundamentals of TypeScript, you can create more reliable and maintainable applications. This tutorial provides a solid foundation for building more complex e-commerce features. With the knowledge gained from this tutorial, you are well-equipped to tackle more complex web development projects. Remember that practice is key, so keep building and experimenting to enhance your skills further. As you refine your skills and expand your knowledge, you’ll be well on your way to creating sophisticated and engaging web applications. Embrace the challenges, learn from your mistakes, and enjoy the journey of becoming a proficient web developer. Your ability to create interactive and user-friendly web applications is a testament to your dedication and skill.