VueJs COMPUTED & WATCHERS Explained!!

ankita srivastava
6 min readMay 11, 2022

When I started working with Vue Js a few months back, the most confusing thing I felt, is to decide when to use computed property and when to use watchers. Most of the time I ran into a situation where I end up writing a watcher, though a watcher was not required there. I used to be very confused thinking of these two and that's the reason I want to pen down my learnings.

https://media.giphy.com/media/dCdjSBHCwQQGBXayHM/giphy.gif

Let’s start understanding what is the difference between computed properties and watchers.

Computed Property

What is computed property?

Computed property enables us to perform any computation with existing data and composes new data with the computed result. In simple terms, you can consider it as a pure function that also creates data for you, so that you can use the computed value wherever required. More practical, less theory. Let’s see an example:

<template>
<div>{{foo}}</div>
</template>

<script>
export default {
components: { EvenOdd },
data() {
return {
bar: 100
}
},
computed: {
foo() {
return (this.bar % 2 === 0) ? 'even' : 'odd'
}
}
}
</script>
Output: even

In the above example, We used the bar data field to compute the value for the foo computed property. Every time whenever there is a change in the bar data field, the computed property will get executed and update the value for foo. Now, we are able to use foo in the template expression because the computed property creates a data field with the same name and then performs the computation on every change of bar data field, which updates the foo value.

Is Computed property always pure functions?

As shown in the above example, the computed property is a pure function, but what if I want to update any data field value inside the computed property?

In most cases, you will not end in a situation where you need to set some data in computed property and that’s why computed properties by default are getters only. For some rare scenarios, we can write a setter like this to update the data value:

<template>
<div>
<div>Employee Details</div>
<div>{{details}}</div>
<button @click="changeEmployeeDetails">Change Details</button>
</div>
</template>

<script>
export default {
name: 'Employee',
data() {
return {
employee: {
name: 'John',
type: 'permanent'
}
}
},
methods: {
changeEmployeeDetails() {
this.details = 'Tom: Contract'
}
},
computed: {
details: {
get() {
return `${this.employee.name}: ${this.employee.type}`
},
set(value) {
const employeeDetails = value.split(': ')
this.employee.name = employeeDetails[0]
this.employee.type = employeeDetails[1]
}
}
}
}

As you can see in the above example, we have a setter that gets a value as a parameter and set it back to the employee data field. This setter will get called only when you try to change computed property generated data field value(details in our case), which we are doing on button clickChange Name.

Best practices

  • Computed property getters should always be pure functions.
  • Try to avoid mutating computed property as much as possible. Instead of mutating the computed property, you should try to update the data field which triggers new computations and invokes the getter. e.g., In the above example, we changed details(computed property) directly in the method invocation, Instead of that, we could have changed the employee object which would have triggered new computation, and updated the details computed property.

How computed property is different from methods?

This is important to understand that computed properties and methods have a different purpose to serve. In a nutshell:

The computed property will always give the same value for the same data field value(pure functions) and will get invoked only when the data field value gets updated.

Methods need not be pure functions and can be invoked any number of times. They do not create data fields for us.

<template>
<div>
<button @click="nowMethod">Display Timestamp</button>
<div>MethodInvocation: {{this.timestamp}}</div>
<div>ComputedProperty: {{now}}</div>
</div>
</template>

<script>
export default {
name: 'Time',
data() {
return {
timestamp: ''
}
},
methods: {
nowMethod() {
this.methodTime = Date.now()
}
},
computed: {
now() {
return Date.now()
}
}
}

Here, every time you click the button Display Timestamp you will see the method invocation happening and the value is getting changed, but the computed property will never get changed because here computed property does not have any reactive dependency (any mutation to data) which triggers it.

The computed property also does cache of data while the method does not and thus every method invocation, re-renders the DOM tree. The computed property will re-render only if the dependent data gets mutated.

Watchers

What are watchers?

Watchers enable us to monitor or spy on a data field value and perform a set of operations when the data field values changes.

We should always try to have computed properties as a pure function, and avoid any mutations. But in rare cases, when we have to perform side-effects e.g., mutations in DOM tree or any other state changes, watchers comes to the rescue.

<template>
<div>
<div>
<span>Celsius:</span>
<input v-model="celsius"/>
<div>{{this.celsius}}</div>
</div>
<div>
<span>fahrenheit:</span>
<input v-model="fahrenheit"/>
<div>{{this.fahrenheit}}</div>
</div>
</div>
</template>

<script>
export default {
name: 'Temperature',
data() {
return {
celsius: 0,
fahrenheit: 32
}
},
watch: {
celsius(newValue, oldValue) {
this.fahrenheit = ((newValue * (9/5)) + 32)
}
}
}
</script>

Here, in the above example, we put a watcher on the celsius data field and when we will update the celsius data field in the input, the watcher will perform the execution and update the Fahrenheit value depending on the changed celsius value. If you feel v-model is something new here, you can consider it as a value attribute for the input tag.

Some important points:

  • The name for the watcher should be the same as the data field which you want to spy on, and thus you can watch one data field at a time.
  • Watcher takes 2 arguments newValue and oldValue based on which you can perform a set of actions.

Configuration in watchers

There are different configurations available for watchers as below:

  • deep: Watchers can be very helpful in mutation, but by default, watchers do not mutate nested properties and if we want to mutate the nested property we need to set deep: true in the watcher as below:
watch: {
someMethod: {
handler(newValue, oldValue) {
.....
},
deep: true
}
}
  • flush: When any data field value gets updated then, the DOM tree gets re-render and the watcher callback also gets executed. By default, the order of execution of these events is in a reverse way i.e., the watcher callback will get executed first and then the DOM tree gets re-render. If incase we are accessing the DOM tree inside the watcher callback, we will get an outdated DOM tree. In such a scenario, we can set flush: 'post' which will help us to get the updated DOM tree in the watcher callback.
watch: {
celsius: {
handler(newValue, oldValue) {
console.log(document.body.innerHTML);
this.fahrenheit = ((newValue * (9/5)) + 32)
},
flush: 'post'
},
}

In the above temperature example, if we have a watcher on celsius which is accessing the DOM tree in the watcher callback without flush: 'post' then we will be below output:

<div id="app" data-v-app="">
<div><div>
<span>Celsius:</span>
<input>
<div>0</div>
</div><div>
<span>fahrenheit:</span>
<input>
<div>32</div>
</div></div>
</div>

After adding flush: 'post' property, it consoles the below output, which has a celsius value updated.

<div id="app" data-v-app="">
<div><div>
<span>Celsius:</span>
<input>
<div>1</div>
</div><div>
<span>fahrenheit:</span>
<input>
<div>32</div>
</div></div>
</div>
  • immediate: In general, watchers do get executed only whenever there is a change in the data field on which we are spying. But in a certain case, where we want to force the watcher to run immediately, we can set immediate: true like below:
watch: {
someMethod: {
handler(newValue, oldValue) {
.....
},
immediate: true
}
}

Other than these mentioned configurations, you can also start watchers conditionally using this.$watch() and stop watchers using $watchAPI functions whenever required.

When to use computed v.s watchers?

  • Use computed property, when you want to perform computation based on any data field.
  • Use computed property, when you want to create a new data field based on computation which can be used for any other purpose as well.
  • Use watchers, when you want to spy/observe/monitor any data value.
  • Use watchers, when you want to perform any DOM mutation or data field updates.

This is all I want to share about computed property and watchers. I hope this information helps you also to understand computed property and watchers without any confusion 🙂. Please feel free to comment on your views or any feedback if you have.

--

--