Vue.js 3 Computed Properties Tutorial

In this Vue tutorial we learn about computed properties with logic like methods.

We cover how their caching works and the benefits it provides our application, as well as how to easily read from and write to them with getters and setters.

Lesson Video

If you prefer to learn visually, you can watch this lesson in video format.

Lesson Project

If you want to follow along with the examples in this lesson, you will need an app generated by the Vue CLI . If you already have one from the previous lesson, you can use it instead.

What is a Computed Property

Computed properties are properties that can be bound to the template like data properties, but have logic like methods. We use them to compose data from existing sources.

A major benefit of computed properties is that they are highly performant because they are cached. Vue will only go through the re-render process if a computed property’s dependency changes.

This may sound a bit complicated at first, but computed properties are very easy to use.

How to define a Computed Property

A computed property is defined as a function in the computed option of the component’s config object.

Syntax: computed property
<script>
export default {
  computed: {
    computed_property() {}
  }
}
</script>

As an example, let’s say we allow the user to input their first and last name in two separate input fields. We want to display their full name in the template so we use string interpolation to output each data property.

Example: src/App.vue
<template>
  <p>Full Name: {{ firstName }} {{ lastName }}</p>
  <p>
    <label for="firstName">First Name: </label>
    <input id="firstName" type="text" v-model="firstName">
  </p>
  <p>
    <label for="lastName">Last Name: </label>
    <input id="lastName" type="text" v-model="lastName">
  </p>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    }
  }
}
</script>

When we run the example in the browser, it works as expected.

But we can optimize the component by using a computed property that combines the first and last names into a full name. Then instead of outputting the two names separately in the template, we output the computed property.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
  <p>
    <label for="firstName">First Name: </label>
    <input id="firstName" type="text" v-model="firstName">
  </p>
  <p>
    <label for="lastName">Last Name: </label>
    <input id="lastName" type="text" v-model="lastName">
  </p>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: ''
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}
</script>

When we run the example in the browser, the full name shows as expected. So it basically does the same as the first example.

At first glance, the computed property seems redundant. The first example without the computed property uses less code and is easier to read.

While that’s certainly true, computed properties gives us two important benefits.

  1. Computed properties are cached, Vue will only re-render the text when one of the names change. This makes the component a lot more performant.
  2. Because a computed property is a function, we can add complex functionality that we otherwise can’t with just string interpolation.

A common use case would be to calculate and display the total price of items in a shopping cart.

Computed Property caching

Anything that’s returned from the v-model directive and regular methods aren’t cached. Vue will re-evaluate and re-render it each time an event fires in the rest of the component.

Let’s demonstrate this by adding a counter to the previous example with a method that increases a number when the user clicks a button.

We’ll also move the fullName computed property to the methods option so that it’s recognized as a regular function. And because methods are invoked and not referenced, we have to add parentheses to the string interpolation in the template.

Finally, to help the demonstration, we’ll let fullName log a message to the console each time it’s invoked.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName() }}</p>
  <p>
    <label for="firstName">First Name: </label>
    <input id="firstName" type="text" v-model="firstName">
  </p>
  <p>
    <label for="lastName">Last Name: </label>
    <input id="lastName" type="text" v-model="lastName">
  </p>

  <hr>

  <p>Num: {{ num }}</p>
  <button @click="incrementNum">Increment</button>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: '',
      num: 0
    }
  },
  methods: {
    fullName() {
      console.log('Re-evaluating fullName')
      return this.firstName + ' ' + this.lastName
    },
    incrementNum() {
      this.num++
    }
  }
}
</script>

When we run the example and take a look in the Console tab of the browser’s dev tools, we’ll see the console log message.

Output:
Re-evaluating fullName

If we type something into the two name fields, another console log statement will be added, showing the number of times fullName was re-executed for each character typed. For the name “John Doe” we get the following output.

Output:
Re-evaluating fullName
7 Re-evaluating fullName

If we click on the button a few times, the number increases. For six button clicks we get the following output.

Output:
Re-evaluating fullName
13 Re-evaluating fullName

So fullName is invoked each time we click the button, despite not being related to the counter at all.

To complete the demonstration, let’s put it back in the computed option so that Vue can cache it. We’ll also remove the parentheses from the string interpolation in the template because computed properties are referenced, not invoked.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
  <p>
    <label for="firstName">First Name: </label>
    <input id="firstName" type="text" v-model="firstName">
  </p>
  <p>
    <label for="lastName">Last Name: </label>
    <input id="lastName" type="text" v-model="lastName">
  </p>

  <hr>

  <p>Num: {{ num }}</p>
  <button @click="incrementNum">Increment</button>
</template>

<script>
export default {
  data() {
    return {
      firstName: '',
      lastName: '',
      num: 0
    }
  },
  computed: {
    fullName() {
      console.log('Re-evaluating fullName')
      return this.firstName + ' ' + this.lastName
    }
  },
  methods: {
    incrementNum() {
      this.num++
    }
  }
}
</script>

When we run the example in the browser and type the name “John Doe”, we’ll get the same result as before.

Output:
Re-evaluating fullName
7 Re-evaluating fullName

But if we click the button, the number won’t increase. Vue will only re-execute a computed property when one of its dependencies change, in this case the names. The counter isn’t a dependency so it doesn’t affect the computed property.

But if we change a dependency like the first name to “Johnathan”, the computed property will re-execute on each keypress and give the following output.

Output:
Re-evaluating fullName
12 Re-evaluating fullName

Computed Property getters & setters

So far, our computed properties have been read-only. That’s to say, we only read the computed property from the Logic and display it in the View.

You may find yourself in a scenario where you not only need to display the property, but also change it. Vue makes it easy for us to do this with getter and setter methods for computed properties.

To use getters and setters, we need to change the definition syntax from a function to an object. Inside the object we define two functions called get and set .

Syntax: get and set
// change from function
computed: {
  property_name() {}
}


// to object with the
// get & set functions
computed: {
  property_name: {
    get() {},
    set(value) {}
  }
}

To demonstrate, we will create an example that uses a computed property to concatenate a hardcoded first and last name into a full name.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Tony',
      lastName:  'Stank'
    }
  },
  computed: {
    fullName() {
      return this.firstName + ' ' + this.lastName
    }
  }
}
</script>

Let’s start with the getter method.

The getter method does what the name implies, it allows us to get the value from the computed property. This is the default behavior of a computed property so our logic in the getter method can be the same.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Tony',
      lastName:  'Stank'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      }
    }
  }
}
</script>

When we run the example, the full name shows on the page as expected.

The setter method changes any of the computed property’s dependencies. It expects a single parameter, which is the returned value of the getter.

In our case the returned value is a string that contains two words, separated with a space. We can use the Javascript split function to separate the words into an array and assign them as new values to the data properties.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Tony',
      lastName:  'Stank'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(value) {
        const names = value.split(' ')
        this.firstName = names[0]
        this.lastName  = names[1]
      }
    }
  }
}
</script>

Whenever we assign a new value to fullName , the setter will be invoked and assign the value to the data properties.

As an example, let’s add a method that changes fullName when the user clicks a button.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
  <button @click="changeName">Change Name</button>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Tony',
      lastName:  'Stank'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(value) {
        const names = value.split(' ')
        this.firstName = names[0]
        this.lastName  = names[1]
      }
    }
  },
  methods: {
    changeName() {
      this.fullName = 'Iron Man'
    }
  }
}
</script>

When we click the button in the browser, it will change the name to the one we changed it to in the new method.

The change can come from anywhere, it doesn’t have to be a method specifically. For example, we can change fullName in something like a lifecycle hook.

Example: src/App.vue
<template>
  <p>Full Name: {{ fullName }}</p>
</template>

<script>
export default {
  data() {
    return {
      firstName: 'Tony',
      lastName:  'Stank'
    }
  },
  computed: {
    fullName: {
      get() {
        return this.firstName + ' ' + this.lastName
      },
      set(value) {
        const names = value.split(' ')
        this.firstName = names[0]
        this.lastName  = names[1]
      }
    }
  },
  mounted() {
    this.fullName = 'Iron Man'
  }
}
</script>

If we run the example above in the browser, the name will change as soon as the app is mounted to the DOM.

Don’t worry if you don’t understand the lifecycle hook right now, we cover them in depth in the Lifecycle Hooks lesson .