Computed Properties

vue.js logo

What is a computed property?

The previous lesson used a JavaScript expression to calculate the text of "day" or "days":

<small v-else-if="daysLeft > 0">
  {{ daysLeft === 1 ? "day" : "days" }} left
</small>

Once we go beyond simple expressions like this, we need a way to handle tasks outside of our HTML to keep things more maintainable. Computed properties are ideal for this.

Computed properties are used for logic containing reactive data (such as a ref()). If any of the reactive data it contains changes, it will be re-calculated and update the template.

A method can also be used for this purpose, however methods are called manually and computed values are automatically re-evaulated when a reactive dependency is changed. Computed properties are also cached so will not run unless a change has occured.

Using computed

We will use a computed property to calculate if events are in the past, and show/hide these with a checkbox. In the App.vue we begin by importing computed from the Vue.js library:

<script setup>
import { ref, computed } from "vue";

Then create a const called showPastEvents to store the checkbox value (initially false):

<script setup>
import { ref, computed } from "vue";
import Event from "./components/Event.vue";

// create new const:
const showPastEvents = ref(false);
const eventData = ref([...

Then below our calculateDaysLeft function, set up a computed property:

const filteredEvents = computed(() => {
    return eventData.value;
});

This will return a new value when any of the reactive data inside is changed, and store this value into filteredEvents. Set this to simply return eventData for now. In the <template>, update v-for to loop over this new value:

<Event
  v-for="event in filteredEvents"
  ...
  >

This should display the events as before in the browser as we are still using eventData in the computed property.

Filtering events

We have access to the full power of JavaScript in the Vue.js <script> section, and we can make use of methods such as filter().

Update the computed property to return a filtered version of our eventData depending on the value of our showPastEvents checkbox:

const filteredEvents = computed(() => {
    return eventData.value.filter((event) => {
        const daysLeft = calculateDaysLeft(event.date);
        return showPastEvents.value ? true : daysLeft >= 0;
    });
});

This computed property:

  • Uses the filter() method to create a new array with events that match a condition.
  • For each event:
    • Calculates the days left until the event using the calculateDaysLeft function.
    • Uses the ternary operator to determine if the event should be included:
      • If showPastEvents is true: includes all events (returns true).
      • If showPastEvents is false: only includes events with daysLeft >= 0 (future or today's events).

Test this in the browser by toggling showPastEvents to be true/false.

Toggling past events

We need to allow the user to be able to toggle the past events using a checkbox. Add the <div class="header"> section to our <template>:

<template>
    <div class="events-container">
    <!-- add new section -->
        <div class="header">
            <h1 class="title">Vue Countdown</h1>
            <div class="controls">
                <label class="checkbox-label">
                    <input type="checkbox" v-model="showPastEvents" />
                    Show past events
                </label>
            </div>
        </div>
    <!-- new section end -->
        <Event
            v-for="event in filteredEvents"

Then add the following styles in the <style> section:

.title {
    font-size: 2rem;
    font-weight: 600;
    color: #2c3e50;
    margin: 0;
}

.controls {
    background: rgba(255, 255, 255, 0.1);
    padding: 0.75rem 1rem;
    border-radius: 8px;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.checkbox-label {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    cursor: pointer;
    font-size: 1rem;
    color: #2c3e50;
    user-select: none;
}

input[type="checkbox"] {
    cursor: pointer;
    width: 1.2rem;
    height: 1.2rem;
    border: 2px solid #4e65ff;
    border-radius: 4px;
    appearance: none;
    transition: all 0.2s ease;
    position: relative;
}

input[type="checkbox"]:checked {
    background: #4e65ff;
    border-color: #4e65ff;
}

input[type="checkbox"]:checked::before {
    content: "✓";
    position: absolute;
    color: white;
    font-size: 0.8rem;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
}

Sorting events

You may notice the order of events is not as we would expect from a countdown app. The events are not in the correct order by date. This can be updated using another JavaScript method called sort():

const filteredEvents = computed(() => {
    return eventData.value
        .filter((event) => {
            const daysLeft = calculateDaysLeft(event.date);
            return showPastEvents.value ? true : daysLeft >= 0;
        })
        .sort((a, b) => {
            const daysLeftA = calculateDaysLeft(a.date);
            const daysLeftB = calculateDaysLeft(b.date);
            return daysLeftA - daysLeftB;
        });
});
  • This uses JavaScript's sort() method to arrange events in chronological order.
  • For each pair of values (a and b):
    • Calculates days left for both events
    • Returns the difference (daysLeftA - daysLeftB) to determine order.

Test this checkbox in the browser to toggle past events!

The result is a dynamic list that automatically updates when:

  • New events are added to eventData.
  • The showPastEvents checkbox is switched.
  • Any event dates change.