Write JavaScript like a pro. Javascript Icon

Follow the ultimate JavaScript roadmap.

Exploring VueJS: Data binding with Computed Properties and Watchers

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:

Angular Directives In-Depth eBook Cover

Free eBook

Directives, simple right? Wrong! On the outside they look simple, but even skilled Angular devs haven’t grasped every concept in this eBook.

  • Green Tick Icon Observables and Async Pipe
  • Green Tick Icon Identity Checking and Performance
  • Green Tick Icon Web Components <ng-template> syntax
  • Green Tick Icon <ng-container> and Observable Composition
  • Green Tick Icon Advanced Rendering Patterns
  • Green Tick Icon Setters and Getters for Styles and Class Bindings

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:

for selected state.

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 🚀!

Learn JavaScript the right way.

The most complete guide to learning JavaScript ever built.
Trusted by 82,951 students.

Todd Motto

with Todd Motto

Google Developer Expert icon Google Developer Expert

Related blogs 🚀

Free eBooks:

Angular Directives In-Depth eBook Cover

JavaScript Array Methods eBook Cover

NestJS Build a RESTful CRUD API eBook Cover