On day 4, I will show how to render a list of items. Each row also has a Delete button that deletes the item from the list. When the list is updated, the template rerenders the list reactively.
There is a new Item
type for all applications. An item has an ID, a label, a purchased flag, and a higher priority flag.
type Item = {
id: number;
label: string;
purchased: boolean;
higherPriority: boolean;
}
Create a Reactive List
- Vue 3 application
In the script tag of the ShoppingCart component, I will create an items
ref with an initial value. Then, the template iterates the array to display each element in a list. Each row has a button that does not do anything. Event handling will be covered at a later day.
let items = ref([
{
id: 1,
label: '10 Apples',
purchased: false,
higherPriority: false,
},
{
id: 2,
label: '5 Bananas',
purchased: false,
higherPriority: false,
},
]);
items
is a reactive array that maintains a list of items in a shopping cart. When an item is added or deleted to items, the list is re-rendered in the template.
<script setup lang="ts">
import { Icon } from "@iconify/vue";
import { ref } from 'vue'
type Item = { ... }
let header = ref('Shopping List App')
let items = ref([...])
</script>
<template>
<h1>{{ header }}</h1>
<ul>
<div class="list-item" v-for="item in items" :key="item.id">
<li>{{ item.id }} - {{ item.label }}</li>
<button class="btn btn-cancel" aria-label="Delete">
<Icon icon="ic:baseline-remove" />
</button>
</div>
</ul>
</template>
<style scoped>
div.list-item {
display: flex
}
div.list-item > li {
margin-right: 0.5rem;
}
</style>
The v-for
directive iterates items to display the id and label properties. The :key
tracks the unique item id so that the list does not render unnecessarily.
The delete button is adjacent to the item with a remove icon. This button does nothing because it does not register any event handler.
The script tag imports the Icon component from the iconify/vue
library and displays the remove icon using it.
In the scoped style, the list item has a flex layout and a margin between the <li> element and the button.
- SvelteKit application
I used $state to create a reactive array to store the items in a shopping cart. Then, the template uses the for-each
syntax to iterate the array to display the items in a list.
let items = $state([
{
id: 1,
label: '10 Apples',
purchased: false,
higherPriority: false,
},
{
id: 2,
label: '5 Bananas',
purchased: false,
higherPriority: false,
},
]);
<script setup lang="ts">
import Icon from "@iconify/svelte";
type Item = { ... }
let header = $state('Shopping List App')
let items = $state([...])
</script>
<template>
<h1>{ header }</h1>
<ul>
{#each items as item (item.id)}
<div class="list-item">
<li>{item.id} - {item.label}</li>
<button class="btn btn-cancel" aria-label="Delete">
<Icon icon="ic:baseline-remove" />
</button>
</div>
{/each}
</ul>
</template>
<style>
div.list-item {
display: flex
}
div.list-item > li {
margin-right: 0.5rem;
}
</style>>
The #each
built-in block iterates items to display the id and label properties. The alias of the array element is item and the unique key is (item.id).
The delete button is adjacent to the item with a remove icon. This button does nothing because it does not register any event handler.
The script tag imports the Icon component from the iconify/vue
library and displays the remove icon when it is used.
Any style within the <style> tag is scoped. Similarly, the list item has a flex layout and there is a margin between the <li> element and the button.
- Angular 19 application
items = signal<Item[]>([
{
id: 1,
label: '10 Apples',
purchased: false,
higherPriority: false,
},
{
id: 2,
label: '5 Bananas',
purchased: false,
higherPriority: false,
},
]);
I declared an items
signal with an initial Item
array. The signal
function has a generic T, and it is set to Item[]
. The @for
control-flow syntax is responsible for iterating items and displaying each item in a row.
import { ChangeDetectionStrategy, Component, signal } from '@angular/core';
import { NgIcon, provideIcons } from '@ng-icons/core';
import { matRemove } from '@ng-icons/material-icons/baseline';
@Component({
selector: 'app-shopping-cart',
imports: [NgIcon],
viewProviders: [ provideIcons({ matRemove })],
template: `
<h1>{{ header() }}</h1>
<ul>
@for (item of items(); track item.id) {
<div class="list-item">
<li>{{ item.id }} - {{ item.label }}</li>
<button class="btn btn-cancel" aria-label="Delete">
<ng-icon name="matRemove"></ng-icon>
</button>
</div>
}
</ul>
`,
styles: `
div.list-item {
display: flex;
}
div.list-item > li {
margin-right: 0.5rem;
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
header = signal('Shopping List App');
items = signal<Item[]>([... array element …]);
}
Unlike Vue 3 and Svelte 5, the items
signal is initialized in the ShoppingCartComponent class.
I imported NgIcons
in the imports array and provideIcons({ matRemove })
in the viewProviders
array. The ng-icon
directive displays the remove icon on the button.
The @for
syntax iterates the items
signal to display the item in a list. The track
expression is mandatory in @for
, and the expression tracks the unique item id.
Moreover, the styles
property of the Component decorator can be used to add inline styles. Similarly, div.list-item
and div.list-item > li
inline styles are applied to the list and the list items.
We have successfully updated the shopping cart component to display items in a list.
Github Repos:
- https://github.com/railsstudent/fundamental-vue3
- https://github.com/railsstudent/fundamental-angular
- https://github.com/railsstudent/fundamental-svelte
Top comments (0)