w3resource
Vue Tutorial

List Rendering

Mapping an Array to Elements with v-for

The v-for directive can be used to render a list of items based on an array. The syntax for v-for directive includes item in items, where the source data is denoted by items and the item is the alias for the array element to be iterated:

<ul id="example-1">
  <li v-for="item in items">
    {{ item.message }}
  </li>
</ul>
var example1 = new Vue({
  el: '#example-1',
  data: {
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

We have full access to parent scope properties inside the v-for blocks. Vue-for supports a second argument which is used as an index of the current item.

<ul id="example-2">
  <li v-for="(item, index) in items">
    {{ parentMessage }} - {{ index }} - {{ item.message }}
  </li>
</ul>
var example2 = new Vue({
  el: '#example-2',
  data: {
    parentMessage: 'Parent',
    items: [
      { message: 'Foo' },
      { message: 'Bar' }
    ]
  }
})

of can be used as the delimiter instead of in:

<div v-for="item of items"></div>

v-for with an Object

v-for can be used to iterate through the properties of an object.

<ul id="v-for-object" class="demo">
  <li v-for="value in object">
    {{ value }}
  </li>
</ul>
new Vue({
  el: '#v-for-object',
  data: {
    object: {
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    }
  }
})

Optionally you can add a second argument for the property's name (a.k.a key)

<div v-for="(value, name) in object">
  {{ name }}: {{ value }}
</div>

Another one can be added for index:

<div v-for="(value, name, index) in object">
  {{ index }}. {{ name }}: {{ value }}
</div>

Maintaining State

Whenever Vue is updating a list that is rendered with v-for, it by default uses an"in-place patch" strategy. If the order of the data items changes, rather than moving the DOM elements to match the order of the items, Vue will patches the element in-place and makes sure it reflects what should be rendered at that particular index.

If you want Vue to track each of the node's identity and therefore reuse and reorder existing elements, you will need to provide a unique key attribute for each item:

<div v-for="item in items" v-bind:key="item.id">
  <!-- content -->
</div>

Array Change Detection

Mutation Methods

Observed array's mutation can be wrapped by Vue so that they will always trigger view updates and they are:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

Replacing an Array

Vue also provides non-mutating methods, filter(), concat() and slice(), which do not mutate the original array but always return a new array:

example1.items = example1.items.filter(function (item) {
  return item.message.match(/Foo/)
})

Caveats

Vue cannot detect the following changes in an array due to the limitation in JavaScript:

  1. when an item is directly set with the index e.g vm.items[indexOfItem] = newValue
  2. when the length of an array is modified e.g vm.item.length = newLength
var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})
vm.items[1] = 'x' // is NOT reactive
vm.items.length = 2 // is NOT reactive

To overcome the first caveat, the following will give the same result as vm.items[indexOfItem] = newValue, and also triggers state update in the reactivity system:

// Vue.set
Vue.set(vm.items, indexOfItem, newValue)
// Array.prototype.splice
vm.items.splice(indexOfItem, 1, newValue)

as an alias for the global Vue.set you can use vm.$set.

To solve the second caveat use the splice method

vm.items.splice(newLength)

Object Change Detection Caveats

Vue also cannot detect property addition or deletion:

var vm = new Vue({
  data: {
    a: 1
  }
})
// `vm.a` is now reactive
vm.b = 2
// `vm.b` is NOT reactive

Dynamically adding new root-level reactive properties to a created instance is not allowed in vue but we can add to a nested object using the Vue.set(object, propertyName, value) method:

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})

You could add a new gender property to the nested userProfile using:

Vue.set(vm.userProfile, 'gender','male')

Displaying Filtered/Sorted Results

There are times when we want to display filtered or sorted version of an array without mutating the original data:

<li v-for="n in evenNumbers">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
  evenNumbers: function () {
    return this.numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

To perform a nested for tated please use a method to handle that:

<li v-for="n in even(numbers)">{{ n }}</li>
data: {
  numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
  even: function (numbers) {
    return numbers.filter(function (number) {
      return number % 2 === 0
    })
  }
}

v-for with a Range

The v-for can also take an integer as a loop parameter:

<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

v-for on a <template>>

we can render a block of multiple elements in v-for using the <template>:

<ul>
  <template v-for="item in items">
    <li>{{ item.msg }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

v-for with v-if

whenever v-for and v-if are used in the same node, v-for has a higher priority than v-if.

v-for with a Component

Like any normal element, we can use v-for on a custom component directly:

<my-component v-for="item in items" :key="item.id"></my-component>

You will however need to use a prop to pass any data automatically:

<my-component
  v-for="(item, index) in items"
  v-bind:item="item"
  v-bind:index="index"
  v-bind:key="item.id"
></my-component>

Below is a code example for a simple To-do App

<div id="todo-list-example">
  <form v-on:submit.prevent="addNewTodo">
    <label for="new-todo">Add a todo</label>
    <input
      v-model="newTodoText"
      id="new-todo"
      placeholder="E.g. Feed the cat"
    >
    <button>Add</button>
  </form>
  <ul>
    <li
      is="todo-item"
      v-for="(todo, index) in todos"
      v-bind:key="todo.id"
      v-bind:title="todo.title"
      v-on:remove="todos.splice(index, 1)"
    ></li>
  </ul>
</div>
Vue.component('todo-item', {
  template: '\
    <li>\
      {{ title }}\
      <button v-on:click="$emit(\'remove\')">Remove</button>\
    </li>\
  ',
  props: ['title']
})
new Vue({
  el: '#todo-list-example',
  data: {
    newTodoText: '',
    todos: [
      {
        id: 1,
        title: 'Do the dishes',
      },
      {
        id: 2,
        title: 'Take out the trash',
      },
      {
        id: 3,
        title: 'Mow the lawn'
      }
    ],
    nextTodoId: 4
  },
  methods: {
    addNewTodo: function () {
      this.todos.push({
        id: this.nextTodoId++,
        title: this.newTodoText
      })
      this.newTodoText = ''
    }
  }
})