Table of Contents
- Case 1: Construct a Reversed Item List
- Case 2: Derive the Number of Items Purchasaed
- Case 3: Derive the Number of Items Purchasaed Label
- Resources
- Github Repos
- Github Pages
On day 10, we finally learn an essential concept of reactivity, which is deriving new states from existing states. In Vue 3 and Angular, the computed
function is used to create a read-only reactive state from other states. Those states can be readonly or writable states. In Svelte 5, the derived states are created by $derived
or $derived.by
rune.
There will be three examples of derived states:
- Create an item list that displays from the newest to the oldest
- Derive number of purchased items
- Derive the display text of the number of items purchased. When the count is one, the unit is singular. Otherwise, the unit, item, is pluralized.
Case 1: Construct a Reversed Item List
- Vue 3 application
The application makes a copy of the items ref and reverse it. Then, the template accesses reversed list and displays its content.
<script setup lang="ts">
import { ref, computed } from 'vue'
const items = ref<Item[]>([])
const reverse_items = computed(() => [...items.value].reverse())
</script>
<template v-if="reversed_items.length > 0">
<ul>
<div class="list-item" v-for="item in reverse_items" :key="item.id">
<li>{{ item.id }} - {{ item.label }}
</li>
</div>
</ul>
</template>
First, computed
is imported from vue
,
reverse_items
is a computed that makes a copy of the items
ref and reverses it.
In the template, v-if
directive checks the length of reversed_items
is positive. When the condition is true, v-for
directive iterates the reversed_items
to display the items from newest to the oldest.
- SvelteKit application
<script lang="ts">
let items = $state([] as Item[]);
let reversed_items = $derived([...items].reverse());
</script>
{#if reversed_items.length > 0}
<ul>
{#each reversed_items as item (item.id)}
<div class="list-item">
<li>{item.id} - {item.label}</li>
</div>
{/each}
</ul>
{/if}
The $derived
rune takes the expression to create a reversed item list and assign the result to reversed_list
.
In the template, the #if
built-in syntax determines the length of reversed_items
is positive. When the condition is true, #for
built-in syntax iterates the reversed_items
to display the items from newest to the oldest.
- Angular 19 application
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-shopping-cart',
imports: [FormsModule, NgIcon],
template: `
@if (reverse_items().length > 0) {
<ul>
@for (item of reverse_items(); track item.id) {
<div class="list-item">
<li>{{ item.id }} - {{ item.label }}</li>
</div>
}
</ul>
}
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
items = signal<Item[]>([]);
reverse_items = computed(() => [...this.items()].reverse());
}
Import computed
from @angular/core
.
The reversed_items
computed is created the same way as Vue 3.
In the template, the @if
built-in syntaxv determines the length of reversed_items
is positive. When the condition is true, @for
built-in syntax iterates the reversed_items
to display the items from newest to the oldest.
Case 2: Derive the Number of Items Purchasaed
- Vue 3 application
The number of items purchased can be derived by counting items where the purchased
property is true. In computed
, the callback function uses Array.reduce
to count the number of items where purchased
is true.
Then, another computed derives the display text based on the number of items purchased
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'
const items = ref<Item[]>([])
const num_items_purchased = computed(() =>
items.value.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0),
)
num_items_purchased
is a computed
that represents the number of items purchased. The items
ref is iterated to count the number of times purchased
is true.
- SvelteKit application
num_items_purchased
rune is derived by calling Array.reduce
within the $derived
rune.
<script lang="ts">
let items = $state([] as Item[]);
let num_items_purchased = $derived(
items.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0)
);
</script>
- Angular 19 application
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-shopping-cart',
template: `
...
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
items = signal<Item[]>([]);
num_items_purchased = computed(() => this.items().reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0));
}
num_items_purchased
is a computed
that represents the number of items purchased. The items
signal is iterated to count the number of times purchased
is true.
Case 3: Derive the Number of Items Purchasaed Label
- Vue 3 application
num_items_purchased_label
computed is a display text derived from the num_items_purchased
computed.
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import { ref, computed } from 'vue'
const items = ref<Item[]>([])
const num_items_purchased = computed(() =>
items.value.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0),
)
const num_items_purchased_label = computed(() => {
const unit = num_items_purchased.value === 1 ? 'item' : 'items'
return `${num_items_purchased.value} ${unit} purchased`
})
</script>
When num_items_purchased
is less than 1, the display text is "<number of items purchased> item purchased". When num_items_purchased
is greater than 1, the display text is "<number of items purchased> items purchased".
<div class="header">
<template v-if="num_items_purchased > 0 && num_items_purchased < items.length">
{{ num_items_purchased_label }}</template
>
<template v-else-if="num_items_purchased === 0"
>You have not purchased any items yet.</template
>
<template v-else>You have bought everything in the shopping cart.</template>
</div>
When num_items_purchased
is between 1 and less than total number of items, the template displays the value of num_items_purchased_label.
When num_items_purchased
is 0, the template displays "You have not purchased any item yet.".
When num_items_purchased
is the same as total number of items, the template displays "You have bought everything in the shopping cart.".
- SvelteKit application
num_items_purchased_label
cannot be derived by an expression, so we use $derived.by
to created a derived rune.
<script lang="ts">
let items = $state([] as Item[]);
let num_items_purchased = $derived(
items.reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0)
);
let num_items_purchased_label = $derived.by(() => {
const unit = num_items_purchased > 1 ? 'items' : 'item';
return `${num_items_purchased} ${unit} purchased`;
});
</script>
{#if num_items_purchased > 0 && num_items_purchased < items.length}
{num_items_purchased_label}
{:else if num_items_purchased == 0}
You have not purchased any items yet.
{:else}
You have bought everything in the shopping cart.
{/if}
Similar to the Vue 3 application, if-else-if-else control flow displays the num_items_purchased_label
rune and the static strings.
- Angular 19 application
import { ChangeDetectionStrategy, Component, computed, signal } from '@angular/core';
@Component({
selector: 'app-shopping-cart',
template: `
<div class="header">
@let num = num_items_purchased();
@if (num > 0 && num < items().length) {
{{ num_items_purchased_label() }}
} @else if (num === 0) {
You have not purchased any items yet.
} @else {
You have bought everything in the shopping cart.
}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ShoppingCartComponent {
items = signal<Item[]>([]);
num_items_purchased = computed(() => this.items().reduce((acc, item) => acc + (item.purchased ? 1 : 0), 0));
num_items_purchased_label = computed(() => {
const unit = this.num_items_purchased() === 1 ? 'item' : 'items';
return `${this.num_items_purchased()} ${unit} purchased`;
});
}
The num_items_purchased
getter appears three times in the control flow. Therefore, it is refactored and assigned to a num
variable. When num
is between 1 and total numer of items, the num_items_purchased_label
getter is invoked to display the value. When num
is 0, "You have not purchased any item yet" is displayed. When num
equals to the total number of items, "You have bought everything in the shopping cart." is displayed.
We have successfully demonstrated how to define derived states from other states in the shopping cart component.
Resources
- Vue 3 Computed: https://vuejs.org/guide/essentials/computed
- Svelte $derived rune: https://svelte.dev/docs/svelte/$derived
- Angular Computed: https://angular.dev/api/core/computed
Github Repos
- https://github.com/railsstudent/fundamental-vue3
- https://github.com/railsstudent/fundamental-svelte
- https://github.com/railsstudent/fundamental-angular
Top comments (0)