Reputation: 743
Here is my current template:
<a-droppable v-for="n in curSize" :key="n - 1" :style="{width: `${99.99 / rowLenMap[orderList[n - 1]]}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLenMap[orderList[n - 1]] > 10}">
<some-inner-element>{{rowLenMap[orderList[n - 1]]}}</some-inner-element>
</a-draggable>
</a-droppable>
The problem is that i have to write rowLenMap[orderList[n - 1]]
multiple times, and i'm afraid vue.js engine will also calculate it multiple times.
What i want is something like this:
<a-droppable v-for="n in curSize" :key="n - 1" v-define="rowLenMap[orderList[n - 1]] as rowLen" :style="{width: `${99.99 / rowLen}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLen > 10}">
<some-inner-element>{{rowLen}}</some-inner-element>
</a-draggable>
</a-droppable>
I think it's not difficult to implement technically because it can be clumsily solved by using something like v-for="rowLen in [rowLenMap[orderList[n - 1]]]"
. So is there any concise and official solution?
Upvotes: 74
Views: 98539
Reputation: 14735
Judging by your template, you're probably best off with a computed property, as suggested in the accepted answer.
However, since the question title is a bit broader (and comes up pretty high on Google for "variables in Vue templates"), I'll try to provide a more generic answer.
Especially if you don't need every item of an array transformed, a computed property can be kind of a waste. A child component may also be overkill, in particular if it's really small (which would make it 20% template, 20% logic, and 60% props definition boilerplate).
A pretty straightforward approach I like to use is a small helper component (let's call it <Pass>
):
const Pass = {
render() {
return this.$scopedSlots.default(this.$attrs)
}
}
Now we can write your component like this:
<Pass v-for="n in curSize" :key="n - 1" :rowLen="rowLenMap[orderList[n - 1]]" v-slot="{ rowLen }">
<a-droppable :style="{width: `${99.99 / rowLen}%`, order: orderList[n - 1]}">
<a-draggable :class="{thin: rowLen > 10}">
<some-inner-element>{{rowLen}}</some-inner-element>
</a-draggable>
</a-droppable>
</Pass>
<Pass>
works by creating a scoped slot. Read more about scoped slots on the Vue.js documentation or about the approach above in the dev.to article I wrote on the topic.
Vue 3 has a slightly different approach to slots. First, the <Pass>
component source code needs to be adjusted like this:
const Pass = {
render() {
return this.$slots.default(this.$attrs)
}
}
Upvotes: 13
Reputation: 869
In some situations a v-define
or v-set
would indeed be helpful to prevent executing something repeatedly (although a computed property is sometimes better). You could use v-for
with an array having just that value.
<template v-for="user in [getUser(id)]">
Hello {{ user.name }}!
</template>
would have the same effect as the hypothetical v-set
:
<template v-set="user=getUser(id)">
Hello {{ user.name }}!
</template>
Upvotes: 4
Reputation: 823
Borrowing an example from Mohamed's answer. To get it working in Vue3 with TypeScript I used Array.prototype.map() to create new objects where I call the function.
const app = new Vue({
el: '#app',
data: {
users: [1, 2, 3, 4],
usersData: {
1: {name: 'Mohamed', age: 29},
2: {name: 'Ahmed', age: 27},
3: {name: 'Zizo', age: 32},
4: {name: 'John', age: 13},
}
},
methods: {
getUser(id) {
return this.usersData[id];
},
},
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="{ id, user } in users.map((id) => ({ id: id, user: getUser(id) }))" :key="id">{{ user.name }}, {{ user.age }} years old</div>
</div>
Upvotes: 1
Reputation: 61
How about this:
<div id="app">
<div
v-for="( id, index, user=getUser(id) ) in users"
:key="id"
>
{{ user.name }}, {{ user.age }} years old
<span @click="show(user)">| Click to Show {{user.name}} |</span>
</div>
</div>
CodePen: https://codepen.io/Vladimir-Miloevi/pen/xxJZKKx
Upvotes: 6
Reputation: 329
Just tested using vue3 and works, i think it works universally
{{ (somevariable = 'asdf', null) }}
<span v-if="somevariable=='asdf'">Yey</span>
<span v-else>Ney</span>
It outputs nothing while setting your variable.
mandatory:
opening "("
set your variable
closing ", null)"
Upvotes: 15
Reputation: 5701
This seems like the perfect use case of a child component. You can simply pass your complex computed value(s) as a property to the component.
https://v2.vuejs.org/v2/guide/components.html#Passing-Data-to-Child-Components-with-Props
Upvotes: 5
Reputation: 838
<template>
<div>
<div v-for="item in itemsList" :key="item.id">
{{ item.name }}
<input v-model="item.description" type="text" />
<button type="button" @click="exampleClick(item.id, item.description)">
Click
</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{
id: 1,
name: 'Name1',
},
{
id: 2,
name: 'Name2',
},
],
}
},
computed: {
itemsList() {
return this.items.map((item) => {
return Object.assign(item, { description: '' })
})
},
},
methods: {
exampleClick(id, description) {
alert(JSON.stringify({ id, description }))
},
},
}
</script>
Upvotes: 2
Reputation: 514
Today I needed this and used <template>
tag and v-for
like this
I took this code and
<ul>
<li v-for="key in keys"
v-if="complexComputation(key) && complexComputation(key).isAuthorized">
{{complexComputation(key).name}}
</li>
</ul>
Changed it to this
<ul>
<template v-for="key in keys">
<li v-for="complexObject in [complexComputation(key)]"
v-if="complexObject && complexObject.isAuthorized">
{{complexObject.name}}
</li>
</template>
</ul>
And it worked and I was pleasantly surprised because I didn't know this was possible
Upvotes: 19
Reputation: 2587
I found a very simple (almost magical) way to achieve that, All it does is define an inline (local) variable with the value you want to use multiple times:
<li v-for="id in users" :key="id" :set="user = getUser(id)">
<img :src="user.avatar" />
{{ user.name }}
{{ user.homepage }}
</li>
Note : set
is not a special prop in Vuejs
, it's just used as a placeholder for our variable definition.
Source
: https://dev.to/pbastowski/comment/7fc9
CodePen
: https://codepen.io/mmghv/pen/dBqGjM
Update : Based on comments from @vir us
This doesn't work with events, for example @click="showUser(user)"
will not pass the correct user, rather it will always be the last evaluated user, that's because the user
temp variable will get re-used and replaced on every circle of the loop.
So this solution is only perfect for template rendering because if component needs re-render, it will re-evaluate the variable again.
But if you really need to use it with events (although not advisable), you need to define an outer array to hold multiple variables at the same time :
<ul :set="tmpUsers = []">
<li v-for="(id, i) in users" :key="id" :set="tmpUsers[i] = getUser(id)" @click="showUser(tmpUsers[i])">
<img :src="tmpUsers[i].avatar" />
{{ tmpUsers[i].name }}
{{ tmpUsers[i].homepage }}
</li>
</ul>
https://codepen.io/mmghv/pen/zYvbPKv
credits : @vir us
Although it doesn't make sense here to basically duplicate the users
array, this could be handy in other situations where you need to call expensive functions to get the data, but I would argue you're better off using computed
property to build the array then.
Upvotes: 192
Reputation: 43899
curSize
is an array. Your temporary values comprise a corresponding implied array sizedOrderList = curSize.map(n => orderList[n-1])
. If you define that as a computed, your HTML becomes
<a-droppable v-for="n, index in sizedOrderList" :key="curSize[index]" :style="{width: `${99.99 / rowLenMap[n]}%`, order: n}">
<a-draggable :class="{thin: rowLenMap[n] > 10}">
<some-inner-element>{{rowLenMap[n]}}</some-inner-element>
</a-draggable>
</a-droppable>
Upvotes: -3