I am attempting to iterate through an array from a Vue component without reapeating the element on which I call 'v-for'. I have not found a subsitute for 'v-for' in the official docs API, nor in articles online.
I have this:
<div v-for="(item, index) in items">
<foo :index="index">
<foo/>
</div>
Want this:
<foo :index="0"><foo/>
<foo :index="1"><foo/>
<foo :index="2"><foo/>
//etc...
And have tried this, which does not work:
<foo v-for="(item, index) in items":index="index">
<foo/>
Help is much appreciated! Started coding with Vue.js yesterday.
Your HTML is wrong. You can do this just fine.
<foo v-for="(item, index) in items" :index="index"></foo>
<foo v-for="(item, index) in items">
{{index}}
</foo>
Does this work for you http://jsbin.com/kapipezalu/edit?html,js,console,output
You probably forgot to define props into foo component.
<div id="app">
<foo v-for="(item, index) in items" :index="index"></foo>
</div>
<template id="foo-temp">
<div>
<span>{{ index }}</span>
</div>
</template>
JS:
Vue.component('foo', {
props: ['index'],
template: '#foo-temp'
})
new Vue({
el: '#app',
data: {
items: [
{id: 1, title: 'One'},
{id: 2, title: 'Two'},
{id: 3, title: 'Three'}
]
}
})
Pro Tip: Use Browser Console - It will show you some cool warnings and errors.
Related
I have the following vue3 code:
<template>
{{click}}
<ol>
<li v-for="item in items" :key="item" v-html="item"></li>
</ol>
</template>
<script setup>
const click = ref();
const items = [
'<p>text</p><a #click="click+1">click</a>',
'<p>text2</p><p><router-link :to="{name: \'home\'}">home</router-link></p>'
];
</script>
How can I get the #click and router-link component to be compiled/rendered correctly in the output?
Vue does not render the string content to html. Does anyone have an idea how I can get this to work? I don't wont to hard code the li-items.
Your code is a big red flag. Thats not the way how to work with vue.js
Here an example how it could be done:
<template>
<ol>
<li v-for="item in items" :key="item.link" >
<router-link :to="item.link">{{ item.name }}</router-link>
</li>
</ol>
</template>
<script setup>
const click = ref();
const items = ref([
{
name: "Link1",
link: "/home"
},
{
name: "Link2",
link: "/login"
}
]);
</script>
I have no idea what this code is supposed to do #click="click+1"
Also, the v-html attribute isnt a attribute that is common used. Because its also dangerous and leads to XSS vulnerabilities if you are not carefully.
I am wanting to put multiple components in a wrapper, so it would look like this,
<div class="wrapper">
<component />
<component />
<component />
</div>
I am trying the following,
<component
v-for="person in persons"
:key="person.key"
:is="chooseType(person)"
:person="person"
:feed="person.self ? handle : aFunction(person)"
:publisher="getPublisher(person)"
/>
My problem is that person is returning as undefined when it runs the chooseType component, why would this be, the persons object is not null, so does have children. How can I loop a dynamic component, as I assume I doing it incorrectly?
v-for is used along with :is in dynamic component. Please find below
Vue.component('dynamic1', {
template: '<div>Dynamic 1</div>'
})
Vue.component('dynamic2', {
template: '<div>Dynamic 2</div>'
})
new Vue({
el: "#app",
data() {
return {
persons: [{
name: 'dynamic1'
}, {
name: 'dynamic2'
}]
}
},
methods: {
getComponent(comp) {
return comp
}
}
})
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<div id="app">
<component v-for="person in persons" :is="getComponent(person.name)"></component>
</div>
So I have a page rendering a v-list based on an array like so :
<v-list-tile v-for="item in array">
{{item}}
</v-list-tile>
and a dialog with a v-text-field :
<v-dialog>
<v-text-field v-model="myInput">
</v-text-field>
</v-dialog>
For now it's pretty normal.
But with a performance test, I saw that for every event triggered by a change on myInput model (like a key press) the v-for is also triggered re-rendering the list when they are actually not related.
On my huge array, it's a serious problem and make the UI really laggy. I think it's a normal behavior for a vuejs application, but I was wondering if I could precisely tell wish element to check for re-rendering.
I tried some v-if statements but it didn't do the trick.
I hope that there is an answer to that, i guess i'm missing something.
If you want to test what i'm talking about here is a ready to go html file, please debug it with your debug console, you will see a [vue warn] message of the duplicated key attesting of the fact that the v-for is indeed called for every key press.
Imagine now if the array (here items) is way bigger than that, and wrapped into complex components, making that call is just too heavy on performance when we are just aiming to change the "myInput" value.
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
{{data}}
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
<input v-model="data"></input>
</div>
</body>
<script>
new Vue({
el: '#app',
data: () => ({
data: '',
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
</script>
</html>
Here's a codepen showing the inner loop in its own component
Codepen.io
I've added Date.now() after items[x].message list items to show when the list is being rerendered.
In case codepen ever goes down:
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/vue#2.6.10/dist/vue.js"></script>
</head>
<body>
<div id="app">
Main vue: {{data}}
<loop-component :data="loopdata"></loop-component>
<input v-model="data"></input>
<input v-model="loopdata"></input>
</div>
<script>
Vue.component('loop-component', {
props: ['data'],
data() {
return {
items: [
{message: 'Foo'},
{message: 'Bar'}
]
}
},
template: `
<div>
Loop component: {{ data }}
<ul>
<li v-for="(item, index) in items" :key="index">
{{ item.message + ' Date.now(): ' + Date.now() }}
</li>
</ul>
</div>
`
});
let app = new Vue({
el: '#app',
data: () => ({
data: '',
'loopdata': '',
items: [
{message: 'Foo'},
{message: 'Bar'},
]
}),
});
</script>
</body>
</html>
Try using .lazy modifier to sync after change events.
<input v-model.lazy="data"></input>
https://v2.vuejs.org/v2/guide/forms.html#lazy
EDIT
#IVO GELOV is right, when a component changes, this re-render. The solution is split your component into several child components.
https://v2.vuejs.org/v2/guide/reactivity.html
This is a code using slots to make it look like your example.
HTML
<div id="app">
<new-component>
<ul>
<li v-for="item in items" :key="item">
{{ item.message }}
</li>
</ul>
</new-component>
</div>
Javascript
Vue.component('new-component', {
data: () => {
return {
data: ''
}
},
template: `
<div>
<div>{{ data }}</div>
<slot></slot>
<input v-model="data"></input>
</div>`
})
new Vue({
el: '#app',
data: () => ({
items: [{
message: 'Foo'
},
{
message: 'Bar'
}
]
})
})
Since Vue 2.0+ whenever a change is detected - the whole component is re-rendered. If you want to avoid that - split your component into several child components.
Your example does not prove your point - the fact that there is a warning about duplicate keys inside the v-for does not mean that v-for is re-evaluated on each keypress. To confirm my statement - just change your code like this:
<li v-for="(item,idx) in items" :key="idx">
Now there is no warning.
We are using Vue.js, very nice framework if you ask me. From Knockout.js and WPF I know that a context can be specified for the bindings. How can this be done with Vue.js?
See the example below. Here binding-context is pseudo code for the functionality I am looking for in Vue.
Vue.component('hosting-setup', {
template:
'<wizard>' +
'<wizard-step binding-context="step1" :title="title">' +
'<select :options="choices"></select>' +
'</wizard-step>' +
'<wizard-step binding-context="step2" :title="title">' +
'<select :options="choices"></select>' +
'</wizard-step>' +
'</wizard>',
data: function () {
return {
step1: {
title: 'Choose virtualization software',
choices: ['Virtual Box', 'VMWare'],
choice: undefined,
},
step2: {
title: 'Choose guest operating system',
choices: ['Debian 6', 'Ubuntu 16', 'Windows Server 2012'],
choice: undefined
}
};
}
});
There is no "with" binding equivalent in Vue. There are a few approaches for what you want to do, but for your example I would use a computed to return your data as an array and then use v-for to print out each component passing the relevant data as a prop:
Vue Instance
Vue.component('wizard-step', {
template: `<div>{{title}}</div>`,
props: ['title']
});
new Vue({
el: '#app',
computed: {
wizardSteps() {
return [this.step1, this.Step2]
}
},
data() {
return {
step1: {
title: 'Choose virtualization software',
choices: ['Virtual Box', 'VMWare'],
choice: undefined,
},
Step2: {
title: 'Choose guest operating system',
choices: ['Debian 6', 'Ubuntu 16', 'Windows Server 2012'],
choice: undefined
}
};
}
})
Markup
<wizard-step :title="step.title" v-for="(step, index) in wizardSteps" :key="index"></wizard-step>
Here's the JSFiddle: http://jsfiddle.net/craig_h_411/vzq25go5/
EDIT
If you want to pass the data down to the component directly, you can use v-bind to pass the object and declare the object property names you want to use in the component as props, which maybe gets closer to what you are asking for, so:
Vue.component('wizard-step', {
template: `<div>
{{title}}
<select>
<option v-for="choice in choices" >{{choice}}</option>
</select>
</div>`,
props: ['title','choices']
});
Parent markup
<wizard-step v-bind="step1"></wizard-step>
<wizard-step v-bind="Step2"></wizard-step>
Here's the JSFiddle for that: http://jsfiddle.net/craig_h_411/7dg41j0w/
If you have really nested child, can try to cheat using v-for with 1 item array.
<template v-for="local in [data.nest1.nest2.nest3]">
//normal binding using local.XXXX
</template>
Tested in Vue 2.6.10:
<template>
<wizard>
<wizard-step v-if="props.step1 ? step = props.step1 : false" :title="step.title">
<select :options="step.choices"></select>
</wizard-step>
<wizard-step v-if="props.step2 ? step = props.step2 : false" :title="step.title">
<select :options="step.choices"></select>
</wizard-step>
</wizard>
</template>
NOTE: Even better, for more concise code you could create a sub-component just for and pass title and options:
I.e.
<template>
<wizard>
<wizard-step v-if="props.step1" :step="props.step1" />
<wizard-step v-if="props.step2" :step="props.step2" />
</wizard>
</template>
and your child wizard-step will be:
<template>
<wizard-step :title="step.title">
<select :options="step.choices"></select>
</wizard-step>
</template>
And another improvement, if you are in control on how the data returned is structured, you could return an array of steps (instead of step1 and step2), and you could just simplify further with a for-each:
<template>
<wizard>
<wizard-step v-for="step in props.data" :step="step" />
</wizard>
</template>
I'm a newbie in Vue.js. I have the following lines of code in my HTML and JS file:
HTML
<div id="app">
<ul>
<li v-for="item in items" v-bind:class="{{item.className}}">{{item.text}}</li>
</ul>
</div>
JS
var app = new Vue({
el: '#app',
data: {
items: [
{
className: 'item-1',
text: 'Item 1'
},
{
className: 'item-2',
text: 'Item 2'
},
{
className: 'item-3',
text: 'Item 3'
}
]
}
})
What I want to happen is bind the value of each className to the class attribute of each DOM element. I hope someone could correct me on this.
When using v-bind you don't need to use the {{...}} syntax, because Vue already assumes you will want to use some kind of a property or object.
So you can for example output the value of each className simply like this:
<li v-for="item in items" v-bind:class="item.className">{{item.text}}</li>
Or shorthand version:
<li v-for="item in items" :class="item.className">{{item.text}}</li>
Or if the classes are always going to follow the pattern of item-i:
<li v-for="item, i in items" :class="`item-` + i">{{item.text}}</li>