VueJs, difference between computed property and watcher?

On Vue.js documentation there is an example like below:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar',
    fullName: 'Foo Bar'
  },
  watch: {
    firstName: function (val) {
      this.fullName = val + ' ' + this.lastName
    },
    lastName: function (val) {
      this.fullName = this.firstName + ' ' + val
    }
  }
})

The above code is imperative and repetitive. Compare it with a computed property version:

var vm = new Vue({
  el: '#demo',
  data: {
    firstName: 'Foo',
    lastName: 'Bar'
  },
  computed: {
    fullName: function () {
      return this.firstName + ' ' + this.lastName
    }
  }
})

What are the situations when watchers more suitable than computed properties? How should i decide which to choose? Documentation keeps saying it is more "generic" but does not really put its purpose.


Solution 1:

Computed Properties

A computed property sample:

computed: {
   val () {
     return this.someDataProperty * someOtherVariable
   }
}

what does this particular piece of code do?

  1. It creates a property named val for the component (on the prototype so <vueInstanece>.hasOwnProperty('val') would show false).

  2. It has a dependency tree which consists of reactive properties (data properties, other computed properties) in this case : this.someDataProperty, which means the moment the dependencies change, the computed property will be recalculated.

  3. Although debated, can't have arguments passed to it. So something like

    computed: {
      val (flag) {
        return (flag === 1) 
          ? this.someDataProperty * someOtherVariable 
          : this.someDataProperty * 5
        }
    }
    

    can't be done

[EDIT] See: https://vuejs.org/v2/guide/computed.html#Computed-Setter

Watcher

A watcher sample:

watch: {
   val (n, o) {
     console.log(n, o)
   }
}
  1. It does not create any new property, but it watches the changes over a reactive property.

  2. Watches only one specific property, unlike computed where any dependent property change can cause recalculation.

  3. Has arguments of new and old value.


So computed properties would be the way to go if:

You want a property that depends on other properties always. Like text formatting for a template, which is even the example in your code.

Or reducing variable lengths as this is quite common:

this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty

can be reduced to:

computed: {
  someDeeplyNestedProperty () {
     return this.$store.state.someProperty.someNestedProperty.someDeeplyNestedProperty
  }
}

Not just reduction in variable size, each time the store updates, you will have the latest value in the someDeeplyNestedProperty.


And Watchers are useful if you want to see if one reactive property has changed to a favourable value to know that you're ready to perform an action.

like:

watch: {
  somethingSelected() {
    this.router.push('someOtherRoute')
  }
}

EDIT: I came across some good article by Flavio Copes who listed common use cases for each of them (methods, computed props, watchers):

When to use methods

  • To react on some event happening in the DOM

  • To call a function when something happens in your component. You can call a methods from computed properties or watchers.

When to use computed properties

  • You need to compose new data from existing data sources
  • You have a variable you use in your template that’s built from one or more data properties
  • You want to reduce a complicated, nested property name to a more readable and easy to use one, yet update it when the original property changes
  • You need to reference a value from the template. In this case, creating a computed property is the best thing because it’s cached.
  • You need to listen to changes of more than one data property

When to use watchers

  • You want to listen when a data property changes, and perform some action
  • You want to listen to a prop value change
  • You only need to listen to one specific property (you can’t watch multiple properties at the same time)
  • You want to watch a data property until it reaches some specific value and then do something

Solution 2:

Computed properties have a a very specific purpose: composing new data derived from other data. They are used whenever you have some data and need to transform it, filter it, or otherwise manipulate it before using it in the template.

Computed properties always have to return a value, should not have any side effects, and they have to be synchronous.

So there are quite some situations where computed properties won't help you, for example: your component receives a prop, and whenever the prop changes, your component had to make an ajax request. For this, you would need a watcher.

Watchers are not useful as often as computed properties, so you should always think about whether or not a computed property can solve your problem, and only fall back on a watcher (or sometimes a method) if that is not the case.

Solution 3:

You use a watcher when you want to mutate a value or perform an action based on some other value changing. A good example of this is when you set a value based on a prop and you want to react to any changes:

Vue.component('my-comp',{
  template: '#my-comp',
  props: ['username'],
  created() {
    this.user = this.username;
  },
  watch:{
    username(val){
      this.user = val;
    }
  },
  data(){
    return{
      user: ''
    }
  }
});

See this JSFiddle: https://jsfiddle.net/fjdjq7a8/

That example is a bit contrived and doesn't really work in the real world because we aren't syncing values, so here's a real example where I am using this in one of my open source projects:

Computeds are for arbitrarily manipulating the data itself, so things like concatenating strings and calculating values.

Solution 4:

For the purpose of this example, computed properties are indeed better. In the example that utilizes watchers notice that this line of code:

this.fullName = this.firstName + ' ' + val

is very similar to this:

this.fullName = val + ' ' + this.lastName

Both serve the same purpose, they are watching for changes in the first or last name and update fullName accordingly. But since this will never change and fullName will always be composed by firstName and lastName then we can avoid the fuss and create a computed property. Then every time firstName and lastName change, fullName will be updated automatically.

There are some cases where using watchers is better though. When you want to do some serious computation of write some async code then a watcher might be more suitable.

For example, if you had something like the following:

let app = new Vue({
    el: '#app',
    data: {
        name: ""
    }
});

And you want, every time that name changes, to make an API call with it, get the result and process it, then a watcher is more appropriate:

watchers: {
    "name": function(newValue, oldValue){
         if(newValue != oldValue)} {
            fetch(url, {method: 'post', body: JSON.stringify({name: this.name})}).then(...);
        }
    }
}

To do that with a computed property you would have to implement a computed get() and a computed set() property that would result in more code.

Also notice that in the documentation's example we have a property, fullName that is composed a.k.a computed by two other properties. In my example name is not computed, in the literal sense of the term. We just want to observe it, so using a computed property would be more of a hack instead of a design pattern.