Exploring VueJS: Data binding with Computed Properties and Watchers blog post

Exploring VueJS: Data binding with Computed Properties and Watchers

Maya Shavin

23 Oct, 2019

Vue

12 minutes read

Our third article of Exploring VueJS series will continue diving into another aspect of data-binding with computed and watch properties in our newly created Vue component – the Pokemon Card. By the end of this post, you will understand what reactive dependencies are and how do they work in Vue.js, and the different use cases of computed and watch properties.

First of all, let’s do a quick recap from our last article, shall we?

Quick review

In our previous post, we created our very first component – PokemonCard, which looks like:

PokemonCard in UI

Besides, we have learned about:

Once you've finished reading this post, do you want to learn even more?

javascript

Learn JavaScript for FREE!

We've created the best online course for you to fully master JavaScript using real-world examples and techniques you’ll find nowhere else.

  • ES2015 and beyond
  • Variables and Scoping
  • Strings, Numbers and Booleans
  • Conditional Statements
  • Operators
  • Modules
  • Arrays and Objects
  • Functions and Closures
  • Loops and Iteration
Get course Get javascript Basics
  • The general concepts of Vue component
  • Data property as our component’s local state management
  • Basic data-binding with v-on and v-bind
  • Functionalities with methods and how to register our component to use within the app. If you miss it, feel free to catch up here before moving forward.

And yes you can always find our code for today’s topic in the series’ Github Repo.

Are you ready? Let’s start.

A new feature request for isSelected

At this point, the component PokemonCard has three local data variables, which are


 title: "Sample pokemon",
 image: "https://res.cloudinary.com/mayashavin/image/upload/v1562279388/pokemons/br5xigetaqosvwf1r1eb.jpg",
 //Flag property to keep track if component is selected
 isSelected: false

And we print out true or false to indicate if a user selects the component. Now let’s say we have a feature request of replacing these boolean values with the following icons in the top left corner:

  • selected icon for selected state.
  • unselected icon for un-selected state.

One solution is to invoking a method which will return the correct icon according to isSelected value in the needed expression:

 <!--<template>-->
 <!--<p>Selected: {{isSelected}}</p>-->
 <img :src="selectedIcon()" />
 //<script>
 methods: {
    selectedIcon() {
    return this.isSelected
    ? "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137286/ExploringVueJS/check-circle-solid.png"
    : "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137284/ExploringVueJS/circle-regular.png";
    }
 }

Simple right? Or so we think.

The problem of method

The solution is simple, but the template is no longer that simple. There are several things to consider here:

  1. selectedIcon() returns a new value for every invoke without any cache; thus, we will always have to rerun this function in every access to it. Take the below example for instance:
<p>First access: {{randomTextMethod()}}</p>
<p>Second access: {{randomTextMethod()}}</p>

In the above code, we simple display a random text generated by calling the method randomTextMethod, which is:

methods: {
 randomTextMethod: () => Math.random(100)
}

Here are the quiz for you, will the texts be the same in first and second access?

The answer is no. Each rerun of the method will generate and return a different value. And this applies to each re-rendering of the component too!

The result of using method to compute variable in UI

  1. Missing () in typing is pretty easy to encounter due to the consistency with other data variable convention use in a template, and will cause a bug. Declarative is no longer at its best.

  2. What if we need to add additional attributes – for example alt text to ensure the accessibility of the icon? Certainly, the alt description will be reflecting the correct state too. We certainly can add another method for alt, such as:

 //<script>
 methods: {
    selectedIcon() {
    return this.isSelected
    ? "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137286/ExploringVueJS/check-circle-solid.svg"
    : "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137284/ExploringVueJS/circle-regular.svg";
    },
    selectedIconAlt() {
        return this.isSelected
        ? "Icon for the selected card."
        : "Icon for the non-selected card."
    }
 }

But that will be redundant since the condition is the same in both methods. And it will get worse when there is a need to modify the comparison logic. Any change in the logic will require to update in more than one location (here it will be in selectedIcon and selectedIconAlt. That will lead to complexity and hidden bug for code maintaining. After all, less code is less chance for bugs to happen.

So is there any better way? Well, welcome to computed property.

Compute your data on the fly

Similar to method, a computed property is a function returns a value and locates inside computed object. There is change in the syntax, thus we can safely move selectedIcon code from methods to computed:

/* PokemonCard.vue */
//inside <script>
export default {
 data: { /*...*/ },
 computed: {
 selectedIcon() {
 return this.isSelected
    ? "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137286/ExploringVueJS/check-circle-solid.svg"
    : "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137284/ExploringVueJS/circle-regular.svg";
 }
 }
}

But unlike method, computed property has its own getter, which allows it to be called in a similar manner with variable – without ():

<img :src="selectedIcon" />

The end result? It will be exactly the same. But not always.

Why? Let’s go back a bit on one of our examples above, with randomTextMethod. We will add a new computed property called randomTextComputed which is identical with randomTextMethod:

methods: {
 randomTextMethod: () => Math.random(100)
},
computed: {
 randomTextComputed: () => Math.random(100)
}

Similarly, in <template> tag, we will print randomTextComputed out twice in a separate section besides the previous code.

<section>
 <h3>Using methods</h3>
 <p>First access: {{randomTextMethod()}}</p>
 <p>Second access: {{randomTextMethod()}}</p>
</section>
<section>
 <h3>Using computed</h3>
 <p>First access: {{randomTextComputed}}</p>
 <p>Second access: {{randomTextComputed}}</p>
</section>

And 💥, the result will be:

Random text generated using computed property and method

Edit Vue Template

As we can see, while the results are different for randomTextMethod, the return values are very consistent for randomTextComputed regardless of how many times we access it.

Return values of computed properties, unlike methods, are cached based on their reactive dependencies.

Thus selectedIcon will re-evaluate only when isSelected changes. Besides, randomTextComputed will always return the previously computed result since there is no reactive dependency,

So, what is a reactive dependency, and how does it work? Let’s find out.

Reactive dependencies at its best

What is reactive dependency?

As mentioned before, Vue.js follows the MVVP (Model-View-View-Model) approach strictly. When the model changes, the related view updates. Each of the models is a property defined in the Vue component’s data object, and hence will be connected to the Vue reactive system automatically upon creation.

In short, a reactive dependency of a computed property is a responsive property (of data or another computed property) which will affect or decide its return value.

Nice, isn’t it? But how does Vue compiler keep track of all the dependencies of a computed property?

How dependency-tracking works

By modifying Object.defineProperty of its data properties and convert them to getters/setters, respectively, the system can enable this connection easily. Let’s take our selectedIcon as an example.

During the creation of the component and the first time when we call selectedIcon, isSelected value is accessed. In other words, selectedIcon triggered the getter of isSelected. This action will notify the system to register isSelected as a reactive dependency of selectedIcon.

Since then, whenever the value of isSelected changes, the setter of isSelected is triggered knowing that selectedIcon relies on isSelect. As a result, the system will re-evaluate the value of selectedIcon based on the new information and then decide on necessary re-rendering.

In short, for every component instance, there is a watcher attached to each property’s setter and getter. This watcher will record any read/write (access/modify) attempt, notify the system on whether to re-render the component. The diagram below demonstrates how it works perfectly:

How reactive dependencies work

Notes on reactive dependencies

Because Object.defineProperty is an ES5 feature, Vue.js doesn’t work with IE8 and below. Bear in mind that if support for older versions of IE is critical to your application.

Important note: The Vue system updates the DOM accordingly and asynchronously. Whenever there is a data property change, the component will not re-render immediately. Instead, the system will open a distinct watcher queue and buffer all the changes that happened within the same event loop.

Since the queue contains unique elements, a watcher will be pushed into the queue only once, regardless of how many times it is triggered. Then, only in the next ‘tick’ of the event loop, the Vue system will flush the queue and do the actual update of that component on the DOM. It is a significant move to avoid unnecessary complex computations and DOM manipulations, which are known to be costly for the application’s performance in the long run.


So far, so good? Great.

Here comes the next question: Is there any other way to monitor data changes without the need of creating a computed property? For example, if we want to observe specific data changes to fire an event or to log the information, and have no use for another computed data?

Well, here comes the watch property.

Watch your data with watch properties

What is watch property?

First of all, Vue inherits the watch concept from AngularJS, as a more generic way (or the only way in AngularJS) to establish a custom watcher for specific data updates on a Vue component.

The syntax is simple and straightforward, watcher for a specific data property will locate as a property inside watch object. And each watch property is a function whose name will be identical to the data property it is observing. For instance, let’s get back to our PokemonCard component and add a watcher for isSelected, to log its status to the console.

export default {
 data: {
 /*...*/
 isSelected: false,
 },
 /*...*/
 watch: {
 isSelected(val) {
 console.log(val);
 }
 }
}

The val paramater passed to the watcher for isSelected is the new value of isSelected. This watcher will be triggered right after isSelected‘s value is updated. Easy enough right?

So why and when should we choose to use watch property over computed?

watch vs. computed property

By definition, watch property, as a normal function, provides us a generic custom way of observing and reacting to data changes on a component instance. Meanwhile, computed property is a function with its getter and setter and returns a value. Computed property allows us to cache and update specific data whose value depends on other reactive data property, in a more declarative manner.

In most of the times, the best practice is to use computed property, for readability and performance optimization. Nevertheless, when there is a need to perform asynchronous API requests or complex operations, wait with intermediary states until those requests or actions completes. An excellent use case is displaying different loading statuses while waiting for extra information from the server based on the user’s interaction. With computed property? It will be impossible to achieve such a thing.

In general, unless it’s a unique case discussed above, it’s recommended to favor using computed property over method or watch property when there is data dependency.

Great. Before we conclude our session, let’s review how our component looks like at this point and make it pretty if needed, shall we?

Component with a style

After adding selectedIcon, in the browser, our PokemonCard will look like below:

PokemonCard with computed property

With our JavaScript code as following:

export default {
 data() {
 return {
 title: "Sample pokemon",
 image:
 "https://res.cloudinary.com/mayashavin/image/upload/v1562279388/pokemons/br5xigetaqosvwf1r1eb.jpg",
 //Flag property to keep track if component is selected
 isSelected: false
 };
 },
 methods: {
 toggleSelect() {
 this.isSelected = !this.isSelected;
 },
 },
 computed: {
 selectedIcon() {
    return this.isSelected
    ? "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137286/ExploringVueJS/check-circle-solid.png"
    : "https://res.cloudinary.com/mayashavin/image/upload/w_24,ar_1:1,c_scale/v1566137284/ExploringVueJS/circle-regular.png";
    }
 },
};

Obviously, we need a bit of styling touches, such as moving the icon to top right corner, with a surrounding margin of 5px. To achieve that, we will add a new class select-icon to the newly added img tag:

<img :src="selectedIcon" class="select-icon"/>

In the <style> section:

.select-icon {
 position: absolute; /*position the icon relative to its first non-static positioned ancestor*/
 top: 0px; /*position the icon within 0px from the top position of its first positioned ancestor*/
 right: 0px; /*position the icon within 0px from the right position of its first positioned ancestor*/
 margin: 5px;
}

That’s it? Not yet. We do need to position our card container to relative. This CSS style ensures our icon’s position will align with our card, not to any other ancestor:

.card {
 position: relative;
 /* same as before*/
}

.select-icon {
 position: absolute;
 top: 0px;
 right: 0px;
 margin: 5px;
}

And 🎉, our component is styled!

PokemonCard after icon styling with position

Conclusion

By exploring the differences between a computed property and method in detail, we learned about how Vue.js implements reactive dependency under the hook, hence continue to improve our component code. Besides, we also get to understand when and why to use watch property or computed property, for better coding habit and for optimizing the app’s performance.

You are invited to try it out the code for the pokemon app during this post here.

What’s next

Since we only cover the primary configuration options up to this point, every PokemonCard component instance has the same data information value. What about reusing and passing different information to different PokemonCard instances? In the next post, we will uncover data-binding with props from the external component and how to use VueDevtools to debug data changes in the app’s components.

Until then, thank you so much for reading, stay tuned, and see you in the next post 🚀!

About the author

Maya is a Senior Frontend Developer @Cloudinary, founder and organiser of VueJS Israel, core maintainer of StorefrontUI and blogger on Frontend Weekly and Ultimate Courses where she mainly writes articles about JavaScript tutorials and good practices. She has been developing web apps with Angular, VueJS and recently ReactJS. She loves to learn and experiment with new framework, while believing that a strong Vanilla JavaScript knowledge is necessary for being a good web developer. When not coding, she enjoys traveling, reading manga, and sketching.

Love the post? Share it!

Lots of time and effort go into all our blogs, resources and demos,
we'd love it if you'd spare a moment to share them!

Explore our Angular courses

Get started today and join over 60,000 developers.