Vue.js - Understanding Data, Methods, Computed and Watch

Understanding the cause, and the effect!

Sujithkumar Sundaran
The Startup
6 min readJul 12, 2020

--

Hey Everyone,

This post explains Why, How and When to use these properties. This post will be a good stop, whether you are a newbie or an experienced Vue.js developer.

We are going to look at how data, methods, computed, and watch works in Vue and what we might miss out when working with it.

Our roadmap will be —

  1. Data
  2. Methods
  3. Computed Property
  4. Watch

Let’s go!

1. Data

We define all our component-specific variables inside data, which returns an object. For example,

// ToyOne.vue<template></template><script>
data () {
return {
amount: 2,
name: 'Dummy Toy'
}
}
</script>

Let me brief on why we are using data as a function when it returns only an object.

// why this
data () {
return {
a: 21
}
}
// instead of this
data: {
a: 21
}

Consider you are setting data like an object, so when that specific component is used multiple times, “a” gets shared on every single instance (That’s how JS works 😒).

The functional representation of data makes it available like an internal state for that specific file. So changing the variable “a” from file-1 will not affect the variable “a” in file-2.

These data properties are reactive. Meaning, any change in the variables declared inside data will reflect immediately in our view/page of that specific file.

2. Methods

We have two methods — increaseCounter() and decreaseCounter()

So, every time when these methods are triggered, the “counter” variable is incremented and decremented respectively.

Never use methods directly, kittens could die!

Extending the above example,

<template>
...
<h2>Method: {{ someCalculation() }}</h2>
...
</template>
<script>
...
methods: {
someCalculation () {
let someData = (this.counter * 1000000)/2.456 - 1.987
return someData
}
}
</script>

Since we have called “someCalculation()” directly instead of using it as a trigger to any events (click, hover, etc..), it will run all the time on our page independently.

Consider using it in a v-for loop and the method returns a result after some complex operations. So each time the method is called, it goes for a full swing of operations inside it before returning the final data — all the time.

A method called directly will trigger on each render. This means, even if we call any other method or do any operation on the page, a directly used method will run all the time.

That’s an inappropriate way of using methods.

Some appropriate ways would be,

  • Using methods as a trigger-point like a click, hover, or any events as shown in the above “counter” example.
  • It can be used for performing/triggering any asynchronous operations like API fetch, etc.

To solve the above problem we use Computed properties.

3. Computed Property

Computed properties are also functions but they are different from methods. They run only once per page load, and it will re-run only if any of it’s dependent variable changes.

<template><div>FullName: {{ fullName() }}</div></template>data () {
return {
firstName: 'Hello'
lastName: 'World'
}
},
computed: {
fullName () {
return this.firstName + ' ' + this.lastName
}
}

fullName()” runs exactly once per page load and after that, it caches the result and returns the cached result for subsequent requests. It will re-run only if the firstName or the lastName changes.

— Computed property on a v-for

One of the best places to use computed property is on v-for’s (It solves the problem we faced in the methods above)

As said earlier, computed runs exactly once on page load — here filterOutOddNums() returns a filtered array of even numbers.

On the initial page load, it filters out the numbers and caches the result for subsequent requests. And if “arrayData” changes for some reason, it will re-run again.

— What we should not do when using v-for

Be careful about using v-for’s. Avoid using “if” conditions inside it. Consider the below example,

<div v-for="(number, index) in anArray" :key="index">
<h2 v-if="number % 2 === 0">Even Number: {{ number }}</h2>
</div>
data () {
return {
anArray: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
}
}

Each time it will loop through all the 10 numbers before reaching the condition inside the loop, and the final result printed out will be just 5 numbers.

We just wasted time and lost performance to loop through the other 5 numbers only to remove them, and this loop runs all the time.

What if the array contains 100 items or more and we filter out only 20 or 50.? — We always loop through 100 items just to print the filtered remainings.

To avoid this, in the example before, we used computed property to filter out the data initially and return only the filtered ones to loop through.

By doing so, will save a lot of computation time and provide a better performance thinking on a large scale.

— Computed properties as Getter/Setter

By default, computed properties act as a getter. Meaning, we cannot pass any parameter to computed properties. All it does is, return any computed data that we can use later.

To use a setter, we have to change the structure a bit,

In increaseCounter() and decreaseCounter() method we assign the counter value to randomMultiples() computed property.

And, yes we don’t pass values as a parameter, instead, we assign value to the computed property as we do for an object. It will receive the assigned value in set(assigned-value)

— When not to use computed properties

  1. Do not use computed properties for asynchronous operations
  2. If your computed property does not contain any reactive dependencies (a fancy way of saying, whether any data depends on this computed property to reflect in the UI), there is no point for that to be computed.

4. Watch

As the name suggests, it just watches over a property for any changes.

If you want to perform an asynchronous operation immediately in response to any data change, go for watchers.

...
<input type="text" v-model="search">
...
data () {
return {
search: null
}
},
watch: {
search (newValue, oldValue) {
if (newValue.length) {
newValue !== oldValue ? hitSearchApiMethod(newValue) : null
}
if (newValue.length <= 0 && oldValue.length) {
hitSearchApiMethod(oldValue)
}
}
},
methods: {
hitSearchApiMethod (value) {
hitApi(value)
}
}

Here, we watch for any data change on “search”.

We receive two parameters current-value and previous-value.

First, we check if the search has a value, then we check if prev and current values are not equal. If these conditions are met we hit the API method.

We also hit the API method if the search is cleared.

— Using handler in watch and why.?

In the above example, we directly change “search”, so Vue is able to detect any change from the watch, but consider another example,

data () {
return {
pagination: {
page: 1,
sortField: 'title',
sortDirection: 'asc',
count: 20
}
}
},

When watching on “pagination”, if we change any data inside of it, Vue will not able to detect the change, since it is done inside the object (pagination) and not the object itself. Which we did on “search” in the example before.

To sort this problem, Vue provides a property called “deep”, it accepts a boolean value,

watch: {
pagination: {
deep: true,
handler (newValue, oldValue) {
// any operation
}
}
}

handler() is equal to calling pagination().

watch: {
pagination (newValue, oldValue) {
// operation
}
}
watch: {
pagination: {
deep: true,
handler (newValue, oldvalue) {
// operation
}
}
}
// Both are same, but the purpose of second one is little different

Since we watch for deep objects, we move our logic to the handler() and set the deep property to be true.

This allows Vue to watch for the changes in properties nested deep inside the object.

And that’s it!

If you have made this far, I hope you had a good time reading.

Thanks and GoodBye! See you on a different post. #StaySafe

--

--