50

I know that in VueJS I can loop through an array:

<span v-for="(element, index) in list">{{ element }}</span>

But what if I wanted a list that is comma separated? For example, if list = ["alice", "bob", "chuck"], then the above would output:

<span>alice</span><span>bob</span><span>chuck</span>

What I want, though, is:

<span>alice</span>, <span>bob</span>, <span>chuck</span>

Is this possible?

Ben
  • 1,550
  • 3
  • 15
  • 22

12 Answers12

62

If all you care about is comma separation, use Javascript's built-in join method:

{{ list.join(', ') }}

For arrays of objects, you could do something like this:

{{ list.map(entry => entry.title).join(', ') }}
René Roth
  • 1,979
  • 1
  • 18
  • 25
  • This is a great solution. I was able to use it for 1 level arrays. I did have to find another alternative for arrays of objects. – Gene Parcellano Jun 20 '18 at 22:01
  • Exactly what I was looking for, because I need it inside select – Christoph May 29 '19 at 09:18
  • @UlysseBN is there a reason you felt the need to remove part of my answer? This goes explicitly against the editing guidelines. – René Roth Jun 24 '22 at 11:42
  • @RenéRoth sorry I could not add a related comment, but I removed it because IMHO it does not provide any more of an answer. Meaning the first part is the answer, the second one is a special case you added without link to the question or any comment. Which part of the editing guidelines are you referring to ? – Ulysse BN Jun 24 '22 at 12:00
  • @UlysseBN Edits should only clarify and not change the author‘s intent. The issue of „array of objects“ came up here and spawned a bunch of subpar solutions, so I amended my answer. – René Roth Jun 25 '22 at 17:02
48

You can use conditional rendering to hide last , like following:

var demo = new Vue({
  el: '#demo',
  data: function() {
    return {
      lists: ['Vue', 'Angular', 'React']
    };
  }
})
<script src="https://vuejs.org/js/vue.min.js"></script>
<div id="demo">
  <span v-for="(list, index) in lists">
    <span>{{list}}</span><span v-if="index+1 < lists.length">, </span>
  </span>
</div>
tony19
  • 125,647
  • 18
  • 229
  • 307
Saurabh
  • 71,488
  • 40
  • 181
  • 244
  • 3
    You can avoid usage of `.length` by comparing to 0, and inverting positons of text and command. See https://stackoverflow.com/a/42129590/6320039 – Ulysse BN Mar 15 '19 at 14:58
  • This is actually the best answer, works perfectly, thank you! – K. P. Mar 05 '20 at 10:06
36

You could do it using a v-if attribute with a condition over the first argument, avoiding the usage of .length:

var app = new Vue({
  el: '#app',
  data: {
    list: ['john', 'fred', 'harry']
  }
})
<script src="https://vuejs.org/js/vue.min.js"></script><div id="app">
  <span v-for="(element, index) in list">
    <span v-if="index != 0">, </span><span>{{ element }}</span>
  </span>
</div>
Ulysse BN
  • 10,116
  • 7
  • 54
  • 82
20

What I ended up doing instead was:

<span v-for="element in list" class="item">
  <span>{{ element }}</span>
</span>

And in CSS:

.item + .item:before {
  content: ", ";
}
Ben
  • 1,550
  • 3
  • 15
  • 22
  • 4
    This solution is very elegant, BUT the break line behavior is bad. When you resize the browser, the comma can go to the next line of the paragraph isolated of the last word. – Paulo Cheque Jul 06 '17 at 18:22
  • I did very similar .comma-list > li:not(:last-child)::after { content: ", " } – nclu Jun 01 '18 at 20:10
  • Not a good solution, tends to mess with screen readers too, prevents correct browser handling of text (advanced font features, line breaks, etc). Don't abuse CSS for these tasks. – René Roth Sep 28 '18 at 20:52
  • 1
    @PauloCheque, I wonder why you call it *"elegant"*. To me, because it separates the comma from the element it's simply wrong. I do believe the *elegant* solution here is `.join(', ')`. Because it makes commas stick with the last letter of each item and because it does not add an extra comma. That's *"elegance"* right there. – tao Apr 13 '20 at 19:56
8

Solution using "template"

 <template v-for="(element, index) in list">
   <span>{{element}}</span><template v-if="index + 1 < list.length">, </template>
 </template>
4

Just adding another alternative which I prefer to use:

<span v-for="(item, index) in list">
    {{ item }}{{ (index+1 < list.length) ? ', ' : '' }}
</span>
Gene Parcellano
  • 5,799
  • 5
  • 36
  • 43
3

If you wanted to do it the JS way, you can just do a computed property; you could even continue the span method.

computed {
  listCommaSeparated () { return this.list.join(', '); },
  listCommaSpans () { return '<span>' + this.list.join('</span><span>') + '</span>'; },
},

This would definitely be the preferred way from a rendering performance standpoint.

1

My component:

<template>
<ul v-if="model.length">
    <li v-for="item in model">{{item}}</li>
</ul>
</template>
<style scoped>
ul {
    list-style: none;
}
li {
    display: inline-block;
}
li:not(:last-child)::after {
    content: ", ";
}
</style>
<script>
export default {
    props: ['model']
};
</script>
nclu
  • 1,057
  • 1
  • 8
  • 19
1

Its possible sample
<span v-for="(item,i) in items"> {{(item !='' && i !=0) ? ',' : ''}} {{item.title}} </span>

Rajilesh Panoli
  • 770
  • 10
  • 17
1

If you do want control over how the dom looks (e.g. actually want to achieve the dom structure that you asked about), you can create a functional component like so:

<script>
// RenderList.vue
export default {
  functional: true,
  render(createElement, context) {
    // Read the list of entries by accessing the prop "list"
    const listEntries = context.props.list;

    // Return a custom list of elements for each list entry.
    // Only return a `,` if it's not the last entry.
    return listEntries.map((listElement, idx) => {
      return [
        createElement("span", listElement),
        idx < listEntries.length - 1 ? ", " : "",
      ];
    });
  },
};
</script>

You would use this component like so:

<template>
  <RenderList :list="['alice', 'bob', 'chuck']" />
</template>
tony19
  • 125,647
  • 18
  • 229
  • 307
Moritur
  • 1,651
  • 1
  • 18
  • 31
0

May I suggest using i > 0 as check?

I've wrapped the separator so that {{ ' ' }} can be used for just having a space and avoiding span treated as being empty.

<span
    v-for="(item, i) in list"
    :key="i">
   <span v-if="i>0">{{ ', ' }}</span>
   <span class="text-nowrap">{{ item }}</span>
</span>

or

<span
    v-for="(item, i) in list"
    :key="i">
   <!-- if not wrapped in a span will leave a space before the comma -->
   <span>{{ (i > 0 ? ', ' : '') }}</span>
   <span class="text-nowrap">{{ item }}</span>
</span>
Antony Gibbs
  • 1,321
  • 14
  • 24
0

There are different views where using :after or :before to have a comma is not a good idea with respect to browser not handling it as a string when the window is resized.

If we try to use conditional operator in each iteration to check whether it is the first or the last element, it would be an overhead if we have large number of items. Also the conditions will be re-evaluated whenever we have change detection.

To overcome these 2 things, we can enclose the comma in a separate element and hide it using CSS :last-child selector as

<template v-for="item in list">
    <span class="item">
        {{item}}
        <span class="comma">
            ,
        </span>
    </span>
</template>

and in CSS

.item:last-child .comma {
    display: none;
}
Saksham
  • 9,037
  • 7
  • 45
  • 73