Introduction to Vue.js Computed Properties and Watchers

John Au-Yeung - Jan 25 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Vue.js is an easy to use web app framework that we can use to develop interactive front end apps.

In this article, we’ll look at Vue.js computed properties and watchers.

Computed Properties

To make more template expressions more concise and reusable, we can create computed properties to compute results from existing properties as their value changes.

We can define computed properties in a Vue instance by putting them in the computed property of the options object that we pass into the Vue constructor.

For example, we can write the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

Then we can use it like any other field in our HTML template. So in index.html, we can write:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{message}}</p>  
      <p>{{spelledMessage}}</p>  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

We should then get:

foof-o-o
Enter fullscreen mode Exit fullscreen mode

on our screen.

The spelledMessage method is a getter that’s used by Vue.js. It takes the returned value of it and then put it in place of {{spelledMessage}} .

Therefore, the name of the placeholder in the template must be the same as the name of the method that returns the value that we want in the computed object.

We can also get the value from the returned Vue instance as follows:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    spelledMessage() {  
      return this.message.split("").join("-");  
    }  
  }  
});console.log(vm.spelledMessage);
Enter fullscreen mode Exit fullscreen mode

We should see f-o-o logged from the console.log.

We can bind to computed properties in templates like any other property. Vue knows that vm.spelledMessage depends on vm.message, so the bindings will be updated when vm.message is updated.

The computed getter method doesn’t commit side effects, which makes it easy to test and understand.

Computed Caching vs Methods

One good feature of computed properties is that the result of it is cached.

There’s no caching for results returned from methods.

So instead of using methods, we should use computed properties for cases where we’re computing something from existing properties.

A computed property is only re-evaluated once a reactive property is updated.

For example, if we have:

const vm = new Vue({  
  el: "#app",  
  data: {  
    message: "foo"  
  },  
  computed: {  
    now() {  
      return Date.now();  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

It’ll never update after its first computed since it doesn’t depend on any reactive property from the data property.

Without caching computed properties keep getting computed as reactive properties that they depend on change, which means the app gets slow as reactive properties that the computed property depends on are changing.

Computed vs Watched Property

Watch properties are useful for watching for changes in a property as the name suggests.

However, because of the caching feature of computed properties, we should use them as much as we can.

For instance, we can simplify the following:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1,  
    person: undefined  
  },  
  watch: {  
    name: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${newVal} - ${this.age}`;  
      }  
    },  
    age: {  
      immediate: true,  
      handler(newVal) {  
        this.person = `${this.name} - ${newVal}`;  
      }  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

to:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person() {  
      return `${this.name} - ${this.age}`;  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

As we can see, the first example was much more complex. We have to set immediate to true in each property so that it’ll watch the initial value change. Then we have to define the value change handler for each reactive property.

Then in each handler, we have to set the person property so that we can combine name and age.

On the other hand, with computed properties, we only have one method which returns the fields combined in the way we want.

It’s much more convenient for us and the app that we create is also faster because of caching.

Computed Setter

We can also create a setter function for computed properties.

For example, we can write:

new Vue({  
  el: "#app",  
  data: {  
    name: "Joe",  
    age: 1  
  },  
  computed: {  
    person: {  
      get() {  
        return `${this.name} - ${this.age}`;  
      },  
      set(newValue) {  
        const [name, age] = newValue.split("-");  
        this.name = name.trim();  
        this.age = age.trim();  
      }  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

The getter function returns the same thing as before, but now we also have a setter function.

The setter function splits the newValue, which has the computed value from the getter function, we split the result and set the result back to the corresponding reactive properties.

We can use a setter as follows. We can put the following in src/index.js:

new Vue({  
  el: "#app",  
  data: {  
    firstName: "Joe",  
    lastName: "Smith"  
  },  
  computed: {  
    name: {  
      get() {  
        return `${this.firstName} ${this.lastName}`;  
      },  
      set(newValue) {  
        const [firstName, lastName] = newValue.split(" ");  
        this.firstName = (firstName || "").trim();  
        this.lastName = (lastName || "").trim();  
      }  
    }  
  }  
});
Enter fullscreen mode Exit fullscreen mode

And in index.html, we put:

<!DOCTYPE html>  
<html>  
  <head>  
    <title>Hello</title>  
    <meta charset="UTF-8" />  
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>  
  </head> <body>  
    <div id="app">  
      <p>{{name}}</p>  
      <input type="text" v-model="name" />  
    </div>  
    <script src="./src/index.js"></script>  
  </body>  
</html>
Enter fullscreen mode Exit fullscreen mode

Then when we change the value in the input, we get the new values assigned back to the firstName and lastName fields with the setter.

Conclusion

We can use computed properties to compute values from existing properties.

Returned values from computed properties are cached to reduce the amount of computation required.

Computed properties can also have setters, where we can do things with the new values returned from the computed property getter.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .