Shopping Cart

A complete shopping cart implementation with add/remove items, quantity updates, and dynamic total calculations. Demonstrates reactive data management and user interaction patterns.

Features Demonstrated

  • Array manipulation - Add, remove, update cart items

  • Lambda expressions - Complex calculations directly in templates using reduce, filter, etc.

  • Event handling - Button clicks, quantity changes

  • Conditional rendering - Empty cart state, checkout button

  • List rendering with keys - Efficient cart item updates

  • Two-way binding - Quantity inputs

  • Number formatting - Currency display

  • Component state management - Cart as a service

Code

View Model (shopping-cart.ts)

export interface CartItem {
  id: number;
  productId: number;
  name: string;
  price: number;
  quantity: number;
  image: string;
  maxQuantity: number;
}

export class ShoppingCart {
  cartItems: CartItem[] = [];

  // Add item to cart
  addToCart(product: { id: number; name: string; price: number; image: string; maxQuantity: number }) {
    const existingItem = this.cartItems.find(item => item.productId === product.id);

    if (existingItem) {
      // Increase quantity if item already in cart
      if (existingItem.quantity < existingItem.maxQuantity) {
        existingItem.quantity++;
      } else {
        alert(`Maximum quantity (${existingItem.maxQuantity}) reached for ${existingItem.name}`);
      }
    } else {
      // Add new item
      this.cartItems.push({
        id: Date.now(), // Simple ID generation
        productId: product.id,
        name: product.name,
        price: product.price,
        quantity: 1,
        image: product.image,
        maxQuantity: product.maxQuantity
      });
    }
  }

  // Update item quantity
  updateQuantity(item: CartItem, newQuantity: number) {
    if (newQuantity <= 0) {
      this.removeItem(item);
    } else if (newQuantity <= item.maxQuantity) {
      item.quantity = newQuantity;
    } else {
      item.quantity = item.maxQuantity;
      alert(`Maximum quantity is ${item.maxQuantity}`);
    }
  }

  // Increase quantity
  increaseQuantity(item: CartItem) {
    if (item.quantity < item.maxQuantity) {
      item.quantity++;
    } else {
      alert(`Maximum quantity (${item.maxQuantity}) reached`);
    }
  }

  // Decrease quantity
  decreaseQuantity(item: CartItem) {
    if (item.quantity > 1) {
      item.quantity--;
    } else {
      this.removeItem(item);
    }
  }

  // Remove item from cart
  removeItem(item: CartItem) {
    const index = this.cartItems.indexOf(item);
    if (index > -1) {
      this.cartItems.splice(index, 1);
    }
  }

  // Clear entire cart
  clearCart() {
    if (confirm('Are you sure you want to clear your cart?')) {
      this.cartItems = [];
    }
  }

  // Proceed to checkout
  checkout() {
    console.log('Proceeding to checkout with:', this.cartItems);
    alert('Proceeding to checkout...');
    // In a real app, navigate to checkout page or open checkout modal
  }
}

Currency Value Converter (currency-value-converter.ts)

Template (shopping-cart.html)

Styles (shopping-cart.css)

How It Works

1. Lambda Expressions in Templates

Instead of computed properties in the view model, calculations are done directly in the template using lambda expressions:

Aurelia's lambda expressions support complex operations like reduce, filter, map, every, and some directly in templates. The template automatically tracks dependencies and recalculates when cartItems changes.

2. Array Manipulation

Using array methods ensures change detection:

3. Benefits of Lambda Expressions

Moving calculations to the template has several advantages:

  • Reduced boilerplate - No need for getter methods in the view model

  • Clear intent - Calculations are visible right where they're used

  • Single source of truth - The template directly expresses what data it needs

  • Automatic reactivity - Aurelia tracks all dependencies within lambda expressions

This approach is particularly useful for derived data that's only needed in the view.

4. Efficient List Updates

Using key: id allows Aurelia to track items efficiently:

When items are removed or reordered, Aurelia reuses DOM elements.

5. Quantity Validation

Multiple ways to update quantity with validation:

6. Conditional Rendering

Show different UI based on cart state:

Lambda expressions work seamlessly with conditionals: if.bind="cartItems.length" or if.bind="!cartItems.length".

7. Currency Formatting with Value Converter

The currency value converter formats prices consistently:

The converter uses Intl.NumberFormat for proper currency formatting including the currency symbol, decimal places, and thousands separators. This keeps formatting logic out of the view model.

8. When to Use Lambda Expressions vs Computed Properties

Use lambda expressions in templates when:

  • The calculation is only needed in the view

  • The logic is straightforward and readable inline

  • You want to reduce view model boilerplate

Use computed properties in the view model when:

  • The calculation is complex and would make the template hard to read

  • The value is used in multiple places (template and view model logic)

  • You need to unit test the calculation logic

  • The calculation is expensive and you want explicit memoization

For this shopping cart example, the calculations are simple arithmetic operations that are only displayed to the user, making lambda expressions a great fit. They eliminate boilerplate while keeping the template clear and maintainable.

Variations

Persist Cart to LocalStorage

Add Discount Codes

Cart as a Service

Make the cart available throughout the app:

Last updated

Was this helpful?