The Difference Between Computed Properties, Methods and Watchers in Vue

    Kingsley Silas
    Share

    Want to learn Vue.js from the ground up? Get an entire collection of Vue books covering fundamentals, projects, tips and tools & more with SitePoint Premium. Join now for just $14.99/month.

    For those starting out learning Vue, there’s a bit of confusion over the difference between methods, computed properties and watchers.

    Even though it’s often possible to use each of them to accomplish more or less the same thing, it’s important to know where each outshines the others.

    In this quick tip, we’ll look at these three important aspects of a Vue application and their use cases. We’ll do this by building the same search component using each of these three approaches.

    Methods

    A method is more or less what you’d expect — a function that’s a property of an object. You use methods to react to events which happen in the DOM, or you can call them from elsewhere within your component — for example, from within a computed property or watcher. Methods are used to group common functionality — for example, to handle a form submission, or to build a reusable feature such as making an Ajax request.

    You create a method in a Vue instance, inside the methods object:

    new Vue({
      el: "#app",
      methods: {
        handleSubmit() {}
      }
    })
    

    And when you want to make use of it in your template, you do something like this:

    <div id="app">
      <button @click="handleSubmit">
        Submit
      </button>
    </div>
    

    We use the v-on directive to attach the event handler to our DOM element, which can also be abbreviated to an @ sign.

    The handleSubmit method will now get called each time the button is clicked. For instances when you want to pass an argument that will be needed in the body of the method, you can do this:

    <div id="app">
      <button @click="handleSubmit(event)">
        Submit
      </button>
    </div>
    

    Here we’re passing an event object which, for example, would allow us to prevent the browser’s default action in the case of a form submission.

    However, as we’re using a directive to attach the event, we can make use of a modifier to achieve the same thing more elegantly: @click.stop="handleSubmit".

    Now let’s see an example of using a method to filter a list of data in an array.

    In the demo, we want to render a list of data and a search box. The data rendered changes whenever a user enters a value in the search box. The template will look like this:

    <div id="app">
      <h2>Language Search</h2>
    
      <div class="form-group">
        <input
          type="text"
          v-model="input"
          @keyup="handleSearch"
          placeholder="Enter language"
          class="form-control"
        />
      </div>
    
      <ul v-for="(item, index) in languages" class="list-group">
        <li class="list-group-item" :key="item">{{ item }}</li>
      </ul>
    </div>
    

    As you can see, we’re referencing a handleSearch method, which is called every time the user types something into our search field. We need to create the method and data:

    new Vue({
      el: '#app',
      data() {
        return {
          input: '',
          languages: []
        }
      },
      methods: {
        handleSearch() {
          this.languages = [
            'JavaScript',
            'Ruby',
            'Scala',
            'Python',
            'Java',
            'Kotlin',
            'Elixir'
          ].filter(item => item.toLowerCase().includes(this.input.toLowerCase()))
        }
      },
      created() { this.handleSearch() }
    })
    

    The handleSearch method uses the value of the input field to update the items that are listed. One thing to note is that within the methods object, there’s no need to reference the method with this.handleSearch (as you’d have to do in React).

    See the Pen Vue Methods by SitePoint (@SitePoint) on CodePen.

    Computed Properties

    While the search in the above example works as expected, a more elegant solution would be to use a computed property. Computed properties are very handy for composing new data from existing sources, and one of the big advantages they have over methods is that their output is cached. This means that if something independent of the computed property changes on the page and the UI is re-rendered, the cached result will be returned, and the computed property will not be re-calculated, sparing us a potentially expensive operation.

    Computed properties enable us to make calculations on the fly using data that’s available to us. In this case, we have an array of items that need to be sorted. We want to do the sorting as the user enters a value in the input field.

    Our template looks almost identical to the previous iteration, with the exception that we’re passing the v-for directive a computed property (filteredList):

    <div id="app">
      <h2>Language Search</h2>
    
      <div class="form-group">
        <input
          type="text"
          v-model="input"
          placeholder="Enter language"
          class="form-control"
        />
      </div>
    
      <ul v-for="(item, index) in filteredList" class="list-group">
        <li class="list-group-item" :key="item">{{ item }}</li>
      </ul>
    </div>
    

    The script part is slightly different. We’re declaring the languages in the data property (previously this was an empty array) and instead of a method we’ve moved our logic into a computed property:

    new Vue({
      el: "#app",
      data() {
        return {
          input: '',
          languages: [
            "JavaScript",
            "Ruby",
            "Scala",
            "Python",
            "Java",
            "Kotlin",
            "Elixir"
          ]
        }
      },
      computed: {
        filteredList() {
          return this.languages.filter((item) => {
            return item.toLowerCase().includes(this.input.toLowerCase())
          })
        }
      }
    })
    

    The filteredList computed property will contain an array of items that include the value of the input field. On first render (when the input field is empty), the whole array will be rendered. As the user enters a value into the field, filteredList will return an array that includes the value entered into the field.

    When making use of computed properties, the data you want to compute has to be available, else this will result in an error in your application.

    Computed property created a new filteredList property, which is why we can reference it in the template. The value of filteredList changes each time the dependencies do. The dependency that’s prone to change here is the value of input.

    See the Pen Vue Computed Properties by SitePoint (@SitePoint) on CodePen.

    Finally, note that computed properties let us create a variable to use in our template that’s built from one or more data properties. One common example is creating a fullName from the first and last name of a user like so:

    computed: {
      fullName() {
        return `${this.firstName} ${this.lastName}`
      }
    }
    

    In the template, you can then do {{ fullName }}. The value of fullName will change whenever the value of either the first or last name changes.

    Watchers

    Watchers are useful for cases when you want to perform an action in response to a change that has occurred (for example, to a prop or to a data property). As the Vue docs mention, this is most useful when you want to perform asynchronous or expensive operations in response to changing data.

    In our search example, we can revert to our methods example and set up a watcher for the input data property. We can then react to any changes of value to input.

    First, let’s revert the template to make use of the languages data property:

    <div id="app">
      <h2>Language Search</h2>
    
      <div class="form-group">
        <input
          type="text"
          v-model="input"
          placeholder="Enter language"
          class="form-control"
        />
      </div>
    
      <ul v-for="(item, index) in languages" class="list-group">
        <li class="list-group-item" :key="item">{{ item }}</li>
      </ul>
    </div>
    

    Then our Vue instance will look like this:

    new Vue({
      el: "#app",
      data() {
        return {
          input: '',
          languages: []
        }
      },
      watch: {
        input: {
          handler() {
            this.languages = [
              'JavaScript',
              'Ruby',
              'Scala',
              'Python',
              'Java',
              'Kotlin',
              'Elixir'
            ].filter(item => item.toLowerCase().includes(this.input.toLowerCase()))
          },
          immediate: true
        }
      }
    })
    

    Here, I’ve made the watcher an object (as opposed to a function). This is so that I can specify an immediate property, which will cause the watcher to trigger as soon as the component is mounted. This has the effect of populating the list. The function that’s run is then in the handler property.

    See the Pen Vue Watchers by SitePoint (@SitePoint) on CodePen.

    Conclusion

    As they say, with great power comes great responsibility. Vue gives you the superpowers needed to build great applications. Knowing when to use each of them is the key to building what your users will love. Methods, computed properties and watchers are part of the superpowers available to you. Going forward, make sure to use them well!