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>
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>
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);
}
}
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>
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>
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));
}
}
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:
- Vue event handling: https://vuejs.org/guide/essentials/event-handling
- Svelte events: https://svelte.dev/docs/svelte/basic-markup#Events
- ngSubmit: https://angular.dev/guide/forms/template-driven-forms#submit-the-form-with-ngsubmit
Top comments (0)