DEV Community

Connie Leung
Connie Leung

Posted on

1

Day 6 - User events in Components

On day 6, I will show how Vue 3, SvelteKit, and Angular respond to HTML events in the shopping cart component.

In the shopping cart component, a new item will be added to the item list on the form submit event. Moreover, the item will be removed from the list when a delete button is clicked.

Case 1: Insert an Item in Form Submit

  • Vue 3 application

The script tag adds a saveItem method to append a new item to the items ref. We access the items, newItem, and newItemHighPriority refs in the script tag; therefore, we have to call .value to access their values. The new item consists of the value of newItem and newItemHighPriority, which is pushed to the array.

<script setup lang="ts">
   import { Icon } from "@iconify/vue";
   import { ref } from 'vue';

   const items = ref<Item[]>([])
   const newItem = ref('')
   const newItemHighPriority = ref(false)

   const saveItem = () => {
      items.value.push({
         id: items.value.length + 1,
         label: newItem.value,
         highPriority: newItemHighPriority.value,
         purchased: false,
     })
     newItem.value = ''
     newItemHighPriority.value = false
   }
</script>

<template>
   <form @submit.prevent="saveItem">
    <input v-model.trim="newItem" />
    <label>
      <input type="checkbox" v-model="newItemHighPriority" />
      <span>High Priority</span>
    </label>
    <button>Save Item</button>
  </form>
</template>
Enter fullscreen mode Exit fullscreen mode

The form element registers the @submit.prevent listener to invoke the saveItem function. @submit.prevent triggers the submit event and calls preventDefault to avoid the page reload. The v-model directive binds newItem to the input field and newItemHighPriority to the checkbox.

  • SvelteKit application

The script tag adds a saveItem method to append a new item to the items rune. The new item consists of the value of newItem and newItemHighPriority runes, and it is appended to the array.

<script lang="ts">
    import Icon from '@iconify/svelte';

    type Item = { id: number; label: string; purchased: boolean; higherPriority: boolean };
    let newItem = $state('');
    let newItemHigherPriority = $state(false);
    let items = $state([] as Item[]);

    function saveItem() {
        if (newItem) {
            items.push({
                id: items.length + 1,
                label: newItem,
                purchased: false,
                higherPriority: newItemHigherPriority
            });
            newItem = '';
            newItemHigherPriority = false;
        }
    }

    async function handleSubmit(event: SubmitEvent) {
        event.preventDefault();
        saveItem();
    }
</script>


<form method="POST" onsubmit={handleSubmit}>
     <input id="newItem" name="newItem" type="text" bind:value={newItem} />
    <label>
        <input id="higherPriority" name="higherPriority"
           type="checkbox" bind:checked={newItemHigherPriority}
         />
         <span> Higher Priority</span>
      </label>
      <button class="btn btn-primary">Save Item</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Listening to DOM events is possible in Svelte 5 by starting the event handler name with "on".
The form element has onSubmit that executes the handleSubmit function. The handleSubmit receives a SubmitEvent to prevent page loading by calling preventDefault. Then, it pushes the new item to the items rune by calling the saveItem function.

  • Angular 19 application

Angular handles signal mutation differently than Vue 3 and Svelte.
In Angular, the items signal does not update when new items are appended to the array. The signal is only updated when it receives a new reference for non-primitive values.
Angular signal provides an update method that accepts a callback function to create a new state from the previous state.
In this demo, the callback function's argument is an array of Item. The callback function uses the spread operator to spread the array and appends the new item to the new array. Finally, the items signal is updated with the new value.

import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { matRemove } from '@ng-icons/material-icons/baseline';

type Item = { id: number; label: string; purchased: boolean; highPriority: boolean };

@Component({
  selector: 'app-shopping-cart',
  imports: [FormsModule, NgIcon],
  viewProviders: [provideIcons({ matRemove })],
  template: `
      <form class="add-item-form" (ngSubmit)="saveItem()">
        <input type="text" name="newItem" [(ngModel)]="newItem" />
        <label>
          <input type="checkbox" [(ngModel)]="newItemHighPriority" name="newItemHighPriority" />
          <span> High Priority</span>
        </label>
        <button type="submit">Save Item</button>
      </form>
    }
    `,
   changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);

  newItem = signal('');
  newItemHighPriority = signal(false);

  saveItem() {
    if (!this.newItem()) {
      return;
    }
    const id = this.items().length + 1;
    this.items.update((items) => [
      ...items,
      { 
         id, 
         label: this.newItem(), 
         purchased: false, 
         highPriority: this.newItemHighPriority() 
       },
    ]);
    this.newItem.set('');
    this.newItemHighPriority.set(false);
  }
}
Enter fullscreen mode Exit fullscreen mode

Angular has a ngSubmit event that handles the form submit event. The event handler code calls event.preventDefault() internally to prevent page reload.
The form element uses the banana syntax to register the ngSubmit event and invoke the saveItem method when the event occurs. (ngSubmit)="saveItem" is the syntax that invokes the saveItem method the ngSubmit event occurs.

Case 2: Delete an Item after Button Click Event

  • Vue 3 application

The deleteItem function iterates the items.value array to remove the item with the matching ID. The filter method creates a new array and assigns it to items.value.

<script setup lang="ts">
   import { Icon } from "@iconify/vue";
   import { ref } from 'vue';

   const items = ref<Item[]>([])
   const newItem = ref('')
   const newItemHighPriority = ref(false)

   const deleteItem = (id: number) => {
     items.value = items.value.filter((item) => item.id !== id)
   }
</script>

<template>
  <!-- previous form -->
  <ul>
      <div v-for="item in items" :key="item.id">
          <li>{{ item.id }} - {{ item.label }}</li>
          <button aria-label="Delete" @click="deleteItem(item.id)">
              <Icon icon="ic:baseline-remove" />
          </button>
      </div>
  </ul>
</template>
Enter fullscreen mode Exit fullscreen mode

The button element registers the click event listener to invoke the deleteItem function. When the button button is clicked, @click triggers the function to remove the item from the array.’

  • SvelteKit application

The deleteItem function iterates the items array to remove the item with the matching ID. The filter method creates a new array and assigns it to items.

<script lang="ts">
    import Icon from '@iconify/svelte';

    type Item = { id: number; label: string; purchased: boolean; higherPriority: boolean };

    let newItem = $state('');
    let newItemHigherPriority = $state(false);
    let items = $state([] as Item[]);

    function deleteItem(id: number) {
        items = items.filter((item) => item.id !== id);
    }
</script>

<ul>
   {#each items as item (item.id)}
       <li>{item.id} - {item.label}</li>
        <button
           onclick={() => deleteItem(item.id)}
           aria-label="delete an item"
         >
             <Icon icon="ic:baseline-remove" />
         </button>
     {/each}
</ul>
Enter fullscreen mode Exit fullscreen mode

Listening to DOM events is possible in Svelte 5 by starting the event handler name with "on". "onClick" listens to the click event and executes the event handler code.
In the demo, onClick={() => deleteItem(item.id)} executes an anonymous function that passes the item ID to deleteItem to remove any item with the matching ID.

  • Angular 19 application

Similarly, the items signal uses the update method to create a new array without the items with the matching ID.
The callback function uses the Array.filter method to iterate the array, removes the item, and returns the new array. Similarly, the items signal is updated with the new value.

// Remove the import statements for brevity reasons

type Item = { id: number; label: string; purchased: boolean; highPriority: boolean };

@Component({
  selector: 'app-shopping-cart',
  imports: [FormsModule, NgIcon],
  viewProviders: [provideIcons({ matRemove })],
  template: `
    <!-- the form element -->
    <ul>
      @for (item of items(); track item.id) {
         <li [class]="itemClasses">
             {{ item.id }} - {{ item.label }}
         </li>
         <button aria-label="Delete" (click)="deleteItem(item.id)">
             <ng-icon name="matRemove"></ng-icon>
         </button>
       }
     </ul>
  `,
   changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
  items = signal<Item[]>([]);
  newItem = signal('');
  newItemHighPriority = signal(false);

  deleteItem(id: number) {
    this.items.update((items) => items.filter((item) => item.id !== id));
  }
}
Enter fullscreen mode Exit fullscreen mode

Angular uses the banana syntax to register click event handlers in an HTML template.
(click)="deleteItem(item.id)" registers the click event to invoke the deleteItem when the button is clicked. The item ID is passed to the deleteItem method to be removed from the items signal.

We have successfully updated the shopping cart component to append an item when submitting the form and delete an item when clicking the button.

Resources:

Github Pages:

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

DevCycle image

Ship Faster, Stay Flexible.

DevCycle is the first feature flag platform with OpenFeature built-in to every open source SDK, designed to help developers ship faster while avoiding vendor-lock in.

Start shipping