Computed Properties

Earlier we mentioned that we can also pass objects to our friends list component instead of just an array of strings, so let’s do that. Because some of our friends are at work right now or on vacation, we can use this opportunity to filter them out of our list so we can talk to only the ones who are actually available.

new Vue({
  el: '#app',
  data: {
    friends: [
      { name: 'Alice', atWork: false, onVacation: false },
      { name: 'Bob', atWork: false, onVacation: false },
      { name: 'Dave', atWork: true, onVacation: false },
      { name: 'Monica', atWork: false, onVacation: true }
    ]
  }
})

Our component still looks the same, we just need to make sure to change the template to output our friends’ name property, as we are not just dealing with simple strings anymore. So make sure to change the following line:

<li v-for="friend in friends">
  {{ greeting }} {{ friend.name }}!
</li>

As a reminder, the entire component should look like this:

Vue.component('friend-list', {
  template: `<div>
    <ul>
      <li v-for="friend in friends">
        {{ greeting }} {{ friend.name }}!
      </li>
    </ul>
    <button @click="bye">Say goodbye</button>
  </div>`,
  props: {
    friends: {
      type: Array,
      required: true
    }
  },
  data: function () {
    return {
      greeting: 'Hello'
    }
  },
  methods: {
    bye () {
      this.greeting = 'Bye'
    }
  }
})

You can see it still works the same as before, just that now we are passing in an array of objects as the prop and iterating over that instead. But so far we are not filtering out our busy friends. Let’s do that with an if statement inside the template. Change the v-for loop to look like this:

<li v-for="friend in friends" v-if="!friend.atWork && !friend.onVacation">

Now we can see that only Alice and Bob actually have time to talk to us. Note that we can use both a v-for and a v-if directive on the same element, as long as we keep in mind that v-for has priority over v-if.

Having that if clause in our template however is not very pretty, and if we had more criteria for filtering the statement would quickly grow out of hand. So let’s replace it with something simpler. This is where computed properties come in.

Let’s say we want to be able to loop through our friends like this:

<li v-for="friend in availableFriends">

Don’t worry, we don’t have to maintain another list of friends. We can take care of it like this:

data: function () {
  return {
    greeting: 'Hello'
  }
},
computed: {
  availableFriends: function () {
    return this.friends.filter((friend) => {
      return !(friend.atWork || friend.onVacation)
    })
  }
},
methods: {
  bye () {
    this.greeting = 'Bye'
  }
}

Note the new computed property in our component. This will do the work and expose availableFriends for us to use in our template.

By the way: We can use the arrow function syntax inside of our computed property, but you cannot use it to define the computed property itself. This would throw an error:

computed: {
  availableFriends: () => {
    return this.friends.filter((friend) => {
      return !(friend.atWork || friend.onVacation)
    })
  }
}

That’s because when using the arrow function, this would not be bound to our Vue instance, so calling this.friends would not work. The same goes for data!

Oh and one more thing: Computed properties are cached. This means they will only be computed again if the underlying data changes (in this case our friends list. We could have also used a method to accomplish the same thing, but they would not be cached.

After all these changes, our app should look like this:

//index.html
<html>
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>LearnVue Beginner Course | Lesson 5</title>
  </head>

  <body>
    <div id="app">
      <friend-list :friends=friends></friend-list>
    </div>

    <script src="https://unpkg.com/vue@2.4.1/dist/vue.js"></script>

    <script>
      Vue.component('friend-list', {
        template: `<div>
          <ul>
            <li v-for="friend in availableFriends">
              {{ greeting }} {{ friend.name }}!
            </li>
          </ul>
          <button @click="bye">Say goodbye</button>
        </div>`,
        props: {
          friends: {
            type: Array,
            required: true
          }
        },
        data: function () {
          return {
            greeting: 'Hello'
          }
        },
        computed: {
          availableFriends: function () {
            return this.friends.filter((friend) => {
              return !(friend.atWork || friend.onVacation)
            })
          }
        },
        methods: {
          bye () {
            this.greeting = 'Bye'
          }
        }
      })

      new Vue({
        el: '#app',
        data: {
          friends: [
            { name: 'Alice', atWork: false, onVacation: false },
            { name: 'Bob', atWork: false, onVacation: false },
            { name: 'Dave', atWork: true, onVacation: false },
            { name: 'Monica', atWork: false, onVacation: true }
          ]
        }
      })
    </script>
  </body>
</html>

Notice how the template still looks nice and clean, despite the added logic.

comments powered by Disqus