DEV Community

Connie Leung
Connie Leung

Posted on

3 1

Day 10 - Introduction to reactivity in Vue 3, Svelte 5 and Angular

Table of Contents

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:

  1. Create an item list that displays from the newest to the oldest
  2. Derive number of purchased items
  3. 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>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

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),
)
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode
  • 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));
}
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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>
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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`;
  });
}
Enter fullscreen mode Exit fullscreen mode

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

Github Repos

Github Pages

Top comments (0)

👋 Kindness is contagious

Dive into this thoughtful article, cherished within the supportive DEV Community. Coders of every background are encouraged to share and grow our collective expertise.

A genuine "thank you" can brighten someone’s day—drop your appreciation in the comments below!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found value here? A quick thank you to the author makes a big difference.

Okay