Vue: Computed properties vs. watcher

Vue.js, or just Vue for short, is one of the big three main JavaScript front end libraries (The other two being React and Angular). In my opinion, Vue is a nice middle ground between React and Angular in terms of the learning curves associated with learning and implementing the framework, as well as how many implementation/configuration decisions need to be made by the developer.

Two of the main building blocks when creating a Vue component are computed properties, as well as watchers. Initially, both seem to be very similar, but a few quick examples of their suggested use cases will quickly clear up what a computed property is best suited for, and what a watcher is best suited for.

First, per the Vue documentation, let’s check out their definitions:

Computed Property

A computed property is used to declaratively describe a value that depends on other values. When you data-bind to a computed property inside the template, Vue knows when to update the DOM when any of the values depended upon by the computed property has changed. This can be very powerful and makes your code more declarative, data-driven and thus easier to maintain.

Vue.js documentation

Watcher

While computed properties are more appropriate in most cases, there are times when a custom watcher is necessary. That’s why Vue provides a more generic way to react to data changes through the watch option. This is most useful when you want to perform asynchronous or expensive operations in response to changing data.

Vue.js documentation

One of the best examples to show what a computed property is best used for is to create a method that formats names that you get from either props that are being passed in, or fetched from an external API call. Let’s take a look at a simple example where we rely on an API response to get the names of all super heros:

<!-- COMPUTED PROPERTY EXAMPLE -->
<template>
  <div v-if="formattedSuperHeroNames.length">
    <li v-for="hero in formattedSuperHeroNames" :key="hero.id">{{ hero.heroName }}</li>
  </div>
</template>

<script>
export default {
  name: "Computed",
  data() {
    return {
      superHeros: [],
    };
  },
  async mounted() {
    try {
      response = await fetch("https://akabab.github.io/superhero-api/api/all.json");
      if (response.ok) {
        this.superHeros = await response.json();
      }
    } catch (error) {
      console.log(error);
    }
  },
  computed: {
    formattedSuperHeroNames() {
      let formattedNames = [];
      this.superHeros.forEach((superHero, index) => {
        const name = superHero.toUpperCase();
        formattedNames.push({ heroName: name, id: index });
      });

      return formattedNames;
    },
  },
};
</script>

Notice how within the template of the Vue component that formattedSuperHeroNames is being referenced, however it isn’t defined within the data property as we’d normally expect. Instead it is actually a function – which also shares the same name – that is defined within the computed object. We can treat any method that is defined within the computed property as if it was a normal property defined within the data property!

The above example simply hits an API once the component is mounted and then once the results are retrieved Vue knows to run formattedSuperHeros() which will iterate over the API response and make each super hero name all upper case letters. Imagine if you had an API where it returned a list of employees and each employee had their first name and last name split between two fields and you wanted to combine them – a computed property would be a great way at achieving this.

What about a watcher? A watcher is different than a computed property in the sense that a watcher wacthes another property and then when it changes it will perform some kind of side effect. A really simple example would be: Keep track of the number of clicks. Once the number of clicks reaches 5 THEN make an API call.

Let’s see how this would work:

<!-- WATCHER EXAMPLE -->

<template>
  <div>
    <button @click="incrementClickCount">Click me!</button><br />
    Number of clicks: {{ numberOfClicks }}
  </div>
</template>

<script>
export default {
  name: "Watcher",
  data() {
    return {
      numberOfClicks: 0,
    };
  },
  methods: {
    incrementClickCount() {
      this.numberOfClicks++;
    },
  },
  watch: {
    numberOfClicks(newValue, oldValue) {
      if (newValue === 5) {
        //HIT AN API END POINT!
      }
    },
  },
};
</script>

One important thing to note is that a watcher MUST have the same name of the property that it is watching over. If it does not share the same name, it will never be invoked and instead just sit idle in your code. Vue also gives us access to both the newValue and oldValue of the property that we are watching, which allows us to more easily implement conditional logic as shown in this example. We’re waiting for the user to click the button exactly 5 times, and then once that happens we hit an API. A good example of this would be for some kind of error logging functionality where we want to see if the user is experiencing some kind of UI issue and is repeatedly clicking/tapping a button over and over again.

tl;dr version

A computed property is best suited for formatting/interacting with a pre-existing data property or prop that has been passed into the component via a parent component, but side effects aren’t desired (Nor should they be introduced via a computed property). A watcher is best suited for when a side effect should happen in response to a certain property/prop changing or reaching a certain value.