How do I reverse the order of an array using v-for and orderBy filter in Vue JS?

JavascriptArraysvue.js

Javascript Problem Overview


I am using Vue JS to do viewmodel bindings. In my data object I have an array of items that are sorted in ascending order (oldest to newest) and I'd like to keep it that way for code-based reasons.

var v = new Vue({
    el: '#app',
    data: {
        items: [
            {id: 51,  message: 'first'},
            {id: 265, message: 'second'},
            {id: 32,  message: 'third'}
        ],
    }
}

However, when I display the array in the template I'd like to reverse the order so that it's descending (newest to oldest). I tried the following:

<ol>
    <li v-for="item in items | orderBy -1" track-by="id">

This didn't work since the orderBy filter seems to require a field name as its first argument.

Is there any way to accomplish this in the template using the v-for syntax using the orderBy filter? Or am I going to have to create a custom reverse filter?

Javascript Solutions


Solution 1 - Javascript

Simple and concise solution:

<li v-for="item in items.slice().reverse()">
//do something with item ...
</li>

Solution 2 - Javascript

> Note: The below works in Vue 1, but in Vue 2 filters are deprecated and you > will see: ' Property or method "reverse" is not defined on the > instance but referenced during render.' See tdom_93's answer for > vue2.

You could create a custom filter to return the items in reversed order:

Vue.filter('reverse', function(value) {
  // slice to make a copy of array, then reverse the copy
  return value.slice().reverse();
});

Then use it in the v-for expression:

<ol>
    <li v-for="item in items | reverse" track-by="id">

https://jsfiddle.net/pespantelis/sgsdm6qc/

Solution 3 - Javascript

Update for Vue2

I want to show some ways that you can work with data and not using filters as they are deprecated in Vue2:

inside computed property

Use computed properties in place of filters, which is much better because you can use that data everywhere in component, not only just in template: jsFiddle

computed: {
	reverseItems() {
  	    return this.items.slice().reverse();
  }    	
}

inside Vuex getter property

If you're using Vuex, and you store your data in store.state object. The best way do some transformation with data stored in state is to do that in getters object (for example filtering through a list of items and counting them, reverse order and so on...)

getters: {
	reverseItems: state => {
		return state.items.slice().reverse();
	}
}

and retrieve state from getters in component computed property:

computed: {
	showDialogCancelMatch() {
		return this.$store.state.reverseItems;
	}
}

Solution 4 - Javascript

Instead of reversing the order of the elements for creation, I only change the order of the display.

<ol class="reverseorder">
    <li v-for="item in items" track-by="id">

And my CSS

<style>
.reverseorder {
  display: flex;
  flex-direction: column-reverse;
}
</style>

No need to clone the array and reverse it.

Solution 5 - Javascript

Based on the fact that the directive v-for can accept not only an array but also any other valid JavaScript iterable object (at least in Vue 2.6+ and Vue 3 releases), we can create our own iterable object to loop through a needed array in the opposite direction. I created a very simplified runnable example (for more details - check information about the JavaScript iterator protocol).

class Iterable {
  constructor(arr) {
    this.arr = arr;
  }

  *[Symbol.iterator]() {
    const arr = this.arr;
    for (let i = arr.length - 1; i >= 0; i--) yield arr[i];
  }

  getIterable(isReversedOrder) {
    return isReversedOrder ? this : this.arr;
  }
}

Vue.component('list', {
  props: ['iterable'],
  template: '<ul><li v-for="(el, i) in iterable" :key="`${i}-${el}`">{{ el }}</li></ul>'
});

const app = new Vue({
  data() {
    return {
      todos: new Iterable(['Learn JavaScript', 'Learn Vue', 'Learn Vuex']),
      isReversed: true,
      inputValue: ''
    };
  },

  computed: {
    computedTodos() {
      return this.todos.getIterable(this.isReversed);
    }
  },

  methods: {
    toggleReverse() {
      this.isReversed = !this.isReversed;
    },

    addTodo() {
      this.inputValue && this.todos.arr.push(this.inputValue);
      this.inputValue = '';
    }
  }
});

app.$mount('#app');

<!DOCTYPE html>
<html>
<body style="display: flex; justify-content: center;">
    <div id="app">
        <button @click="toggleReverse">Toggle reverse to {{ !isReversed }}</button>
        <br />
        <input v-model="inputValue" style="margin-top:5px;" />
        <button @click="addTodo" :disabled="!inputValue">Add todo</button>
        <!-- <ul><li v-for="(todo, i) in computedTodos" :key="`${i}-${todo}`">{{ todo }}</li></ul> -->
        <list :iterable="computedTodos" />
    </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
    <script src="script.js"></script>
</body>
</html>

P.S.Try to avoid using such Array.prototype functions as shift/ unshift , reverse etc. to add / remove items from the beginning of the array or reverse the order, especially in the case when such operations are performed frequently and / or an array includes a big quantity of items, because they are quite costly as for performance (have O(n) complexity). Another good solution is to use CSS to display elements in the reversed order (see an answer above).

Solution 6 - Javascript

The v-for directive doesn't support iterating backwards, so if you want to order by newest you're going to need to add another field to indicate when the item was added, or change id to increment every time an item is added.

Then, with field being the field indicting the order added:

<li v-for="item in items | orderBy 'field' -1" track-by="id">

Solution 7 - Javascript

For my use case (which is admittedly, apparently different than the OP...) I wanted to have the indices of the Array in reverse order in the v-for "loop."

My solution was to create a Vue app method reverseRange(length) that returns an Array of integers from length-1 to 0. I then used that in my v-for directive and simply referred to my Array elements as myArray[index] every time I needed it.

That way, the indices were in reverse order and I was able to then use them to access elements of the Array.

I hope this helps someone who landed on this page with this subtle nuance in their requirements like me.

Solution 8 - Javascript

You can use lodash reverse:

<li v-for="item in _.reverse(items)">

Solution 9 - Javascript

Possibly I'm missing some downsides here, but how about iterating over the array from end to start using an index?

<ol>
  <li v-for="i in items.length" :set="item = items[items.length - i]">

Like, if your array consists of thousands of elements, copying it with .slice().reverse() every time is probably not the most efficient approach.

Upd.: note, :set is not an official way for defining variables in template, it just works. As an alternative, the item variable could be replaced by a call to some getItem(i) method that would encapsulate the items[items.length - i] expression.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSoviutView Question on Stackoverflow
Solution 1 - JavascriptNima AjdariView Answer on Stackoverflow
Solution 2 - JavascriptPantelis PeslisView Answer on Stackoverflow
Solution 3 - Javascriptt_dom93View Answer on Stackoverflow
Solution 4 - JavascriptkirschkernView Answer on Stackoverflow
Solution 5 - JavascriptRoman KaragodinView Answer on Stackoverflow
Solution 6 - JavascriptErikOlson186View Answer on Stackoverflow
Solution 7 - JavascriptJohn CarrellView Answer on Stackoverflow
Solution 8 - JavascriptMaxView Answer on Stackoverflow
Solution 9 - JavascriptKlesunView Answer on Stackoverflow