DEV Community

Ahmet Meliksah Akdeniz
Ahmet Meliksah Akdeniz

Posted on • Updated on

Vue.js Basics Part 3 | Computed Property

Today, I will be talking about computed property in Vue.js. First, I will code this tutorial from vuejs.org. Afterwards, I will touch the differences between methods and computed properties in Vue.js (not in detail).

This is part 3 of my Vue.js Basics series, to read part 2, please go to here

Here's the whole code:

<script>
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
  computed: {
    hideCompletedTodos() {
      return this.hideCompleted ? this.todos.filter(item => !item.done) : this.todos
    }
  },
  methods: {
    addTodo() {
      this.todos.push({ id: id++, text: this.newTodo, done: false })
      this.newTodo = ''
    },
    removeTodo(todo) {
      this.todos = this.todos.filter(item => item.id !== todo.id)
    },
    toggleHideCompleted() {
      this.hideCompleted = !this.hideCompleted
    }
  }
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" />
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in hideCompletedTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  <button @click="toggleHideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>
</template>

<style>
.done {
  text-decoration: line-through;
}
</style>
Enter fullscreen mode Exit fullscreen mode

Time to read to read code above in small chunks!

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
Enter fullscreen mode Exit fullscreen mode

In our data(){} property we've got newTodo for text key property in our todos array. hideCompleted key for hiding completed todos, this is a boolean value (true or false). Its task is to hide completed todos if it's true, and show all todos regardless of they're completed or not whhen it's false. Finally, we've got an array of objects that stores our todos. As you notice, we've got a new key (done:) in our objects. That's to alter styling for tasks those are done, later on that one.

Next, computed property:

computed: {
    hideCompletedTodos() {
      return this.hideCompleted ? this.todos.filter(item => !item.done) : this.todos
    }
  },
Enter fullscreen mode Exit fullscreen mode

hideCompletedTodos() function returns something based on hideCompleted's boolean value (true or false). If hideCompleted is true, it filters items those item.done properties are false. In other words, we will have only item.done with falsy values left because item => !item.done this code returns items those item.done values are falsy. : this.todos this code says, else (if hideCompleted is not true), then return todos array altogether.

A bit more about computed. At first glance, computed may look like as if it's the same with methods, but it's not. First of all, computed re-renders only the changed element, on the other hand, methods re-render everything. This becomes a performance issue. To solve this use computed, a computed property tracks reactive state as dependencies, and caches the result. Once when the dependencies change, it updates. However, methods property updates even if the result doesn't change.

Time to get back to our code

methods: {
    addTodo() {
      this.todos.push({ id: id++, text: this.newTodo, done: false })
      this.newTodo = ''
    },
    removeTodo(todo) {
      this.todos = this.todos.filter(item => item.id !== todo.id)
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

addTodo() function adds (actually 'pushes') a new object to the todos array. Finally, it sets this.newTodo to an empty string, so we won't see [object Object] weird thing in the input once the form (we will talk about it soon) is submitted.

removeTodo(todo) function takes a parameter - todo. When 'X' button is clicked, it will bring that particular 'todo' ({ id: id++, text: 'Learn HTML', done: true }... one of these) and filter (remove) it. Since, I've given item parameter in filter method, each to do will be represented as item. Logic behind this code; this.todos.filter(item => item.id !== todo.id), this compares if item.id and todo.id are not the same. Then, it returns items those have different ids.

What the heck does this mean? So, when we click on 'X' button, it returns an object with an id key. As you know filter method iterates over arrays. So, it iterates over todos array, and returns items those ids don't match todo.id that comes from the item parameter. part of this code is done, not let's talk about the template part.

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo" />
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in hideCompletedTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>
</template>
Enter fullscreen mode Exit fullscreen mode

@submit.prevent prevents forms from refreshing pages when form is submitted. Then, we've got an input tag that is two-way-binded to newTodo data. v-model: is responsible for syncing newTodo data and addTodo() function. Outside of our form we have got a list tag.

Here's how v-for works in a list tag:
As you see there's todo in hideCompletedTodos. Yes, we've got todos array, but what is todo? It's a local variable that will be alive only in this

  • tag because it's created in it. That means, todo is not accesible outside of
  • in this case. So, we've got an array of objects, by using v-for, we iterate over objects in hideCompletedTodos array. So, todo represents each object in hideCompletedTodos array. Since todo is a local variable, we can name it however we wish, but understandable naming is one of the best practices. For instance, if I had an array of books, I would have coded it as <li v-for="book in books" :key="book.id"> ... </li>.

    quick reminder

    hideCompletedTodos() {
          return this.hideCompleted ? this.todos.filter(item => !item.done) : this.todos
        }
    
    Enter fullscreen mode Exit fullscreen mode

    The code above returns filtered todos array or todos array in its original form, that's why <li v-for="todo in hideCompletedTodos" :key="todo.id"> v-for loop is like this. So, hideCompletedTodos is either filtered (based on objects done value property) todos array or untouched todos array.

    <input type="checkbox" v-model="todo.done"> to understand this part, I will share part of the code.

    <style>
    .done {
      text-decoration: line-through;
    }
    </style>
    
    Enter fullscreen mode Exit fullscreen mode

    In short, the code above has a CSS class .done that outs line through text. This code v-model="todo.done" binds todo's done property with done CSS class. So, when the checkbox has a tick in it, .done CSS class is added, and a line goes through the text.

    How do we get specific todo from the list by removeTodo(todo)? This is how:

    <li v-for="todo in hideCompletedTodos" :key="todo.id">
    <button @click="removeTodo(todo)">X</button>
    </li>
    
    Enter fullscreen mode Exit fullscreen mode

    As you can notice, todo is a local variable that represents each object in hideCompletedTodos ('hideCompletedTodos' returns filtered todos or original todos array, remmeber?) By using @click method we trigger removeTodo(todo) function that takes its parameter from todo local variable. Their names have to match because todo local variable is the argument for removeTodo(todo)

    <button @click="hideCompleted = !hideCompleted">
        {{ hideCompleted ? 'Show all' : 'Hide completed' }}
      </button>
    
    Enter fullscreen mode Exit fullscreen mode

    The code above changes state of hideCompleted. hideCompleted is a boolean value, so the code above turns it to the opposite of the current value.

    That's it for part 3, we talked about computed properties, and difference between a computed property and a method property in Vue.js.

    Next, I will be going through Life Cycle and Template Refs, and maybe Watchers.

  • Top comments (0)