Adding vue components based on condition in v-for - javascript

I have a JS array formatted similar to
[{type:'text', data: 'some text'},{type: 'image', data: 'link/to/image'}]
for the different values of type I have different vue components (<text-block>, <image-block>) and I want to use a v-for to loop over this array and based on the type, create the right vue component.
The examples for v-for show creating the same element many times like many <li>. Is there a way I can create different elements in a v-for?

You can just use v-if:
<div v-for="(loop, index) in loops" :key="index">
<text-block v-if="loop.type === 'text'"></text-block>
<image-block v-if="loop.type === 'image'"></image-block>
</div>
You can also use dynamic components:
<div v-for="(loop, index) in loops" :key="index">
<component :is="loop.type + '-block'"></component>
</div>
Make sure you have imported the components and defined them on the instance.

You can do something like this, say there's list of movies in an array:
<div id="app">
<ul>
<li v-for="movie in movies">{{ movie }}</li>
</ul>
</div>
<script>
var app = new Vue({
el: '#app',
data: {
movie: ['some movie 1', 'movie 2', 'idk movies man']
}
});
setTimeout(function() {
app.movies.push('random movie');
}, 2000);
</script>

Related

Using v-for inside component that uses <slot/> to create re-usable and dynamic Tab menu

So I'm trying to create a dynamic tab menu with Vue 3 and slots. I got the tabs working, I have BaseTabsWrapper and BaseTab components. I need to be able to v-for with BaseTab component inside of a BaseTabsWrapper Component. Like this:
<section
id="content"
class="w-full mx-2 pr-2"
v-if="incomingChatSessions && incomingChatSessions.length"
>
<BaseTabsWrapper>
<BaseTab
v-for="chatSession in incomingChatSessions"
:key="chatSession.id"
:title="chatSession.endUser.name"
>
<p>{{ chatSession }}</p>
</BaseTab>
</BaseTabsWrapper>
</section>
An important caveat from the answers that I have found is that the incomingChatSessions object is asynchronous and coming from a websocket (I have tested that this object is working fine and bringing all the data correctly aka is never an empty object).
Inside of BaseTabsWrapper template. Important parts:
<template>
<div>
<ul
class="tag-menu flex space-x-2"
:class="defaultTagMenu ? 'default' : 'historic'"
role="tablist"
aria-label="Tabs Menu"
v-if="tabTitles && tabTitles.length"
>
<li
#click.stop.prevent="selectedTitle = title"
v-for="title in tabTitles"
:key="title"
:title="title"
role="presentation"
:class="{ selected: title === selectedTitle }"
>
<a href="#" role="tab">
{{ title }}
</a>
</li>
</ul>
<slot />
</div>
</template>
And the script:
<script>
import { ref, useSlots, provide } from 'vue'
export default {
props: {
defaultTagMenu: {
type: Boolean,
default: true,
},
},
setup(props) {
const slots = useSlots()
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
const selectedTitle = ref(tabTitles.value[0])
provide('selectedTitle', selectedTitle)
provide('tabTitles', tabTitles)
return {
tabTitles,
selectedTitle,
}
},
}
</script>
This is the Tab component template:
<template>
<div v-show="title === selectedTitle" class="mt-4">
<slot />
</div>
</template>
<script>
import { inject } from 'vue'
export default {
props: {
title: {
type: String,
default: 'Tab Title',
},
},
setup() {
const selectedTitle = inject('selectedTitle')
return {
selectedTitle,
}
},
}
</script>
The important part in my script and the one that is giving me a lot of trouble is this one:
const tabTitles = ref(
slots.default()[0].children.map((tab) => tab.props.title)
)
What I'm doing here is creating an array of tab titles based on the property "title" of each slot but when I load the page this array always have just one title, even if I'm fetching more title elements from the API. One thing that I have noticed is that if I force a re-render of the page from my code then the tabTitles array have the correct amount of elements and I got all the correct amount of tabs on my menu. I have tested that everything is working fine with the way I control asynchronicity with the data coming from the websocket in order to hidrate the "incomingChatSessions" array but as much as I try tabTiles always gets just one element no matter what.
i would do something like that :
computed(
() => slots.default()[0].children.map((tab) => tab.props.title)
)
it should update the computed property when the component is updated (like slot changes)

Vue.js: Can I use v-if to create components?

I'm new to front end development and am experimenting with learning Vue.js. Suppose I have an array
fruits = ["New York", "Chicago", "London"]
and I have a component named FruitPrices which can work like:
<FruitPrices fruit="London"></FruitPrices>
I was wondering would it be possible to use v-if to create multiple components?
Something along the lines of:
<div v-for="item in fruits" : key="item">
<FruitPrices fruit="item"></FruitPrices>
</div>
This code does not work so I was wondering if anyone experienced with Vue can lend me a helping hand!
When going through a loop in Vue Js it is required to have a key. For instance, if you had fruits = [{id:1, name: London}, {id:2, name: New York}] you could have done v-for="item in fruits" :key="item.id"
but because you dont have it the best option is the one below:
<div v-for="(item, index) in fruits" :key="index">
<FruitPrices fruit="item"></FruitPrices>
</div>
Vue js is going to consider index as the position of each item in the array
here is your component:
<template>
<div>
<div v-for="item in fruits" :key="item">
<FruitPrices :fruit="item"></FruitPrices>
</div>
</div>
</template>
<script>
import FruitPrices from "../somePlace/FruitPrices";
export default {
components: { FruitPrices },
data: () => ({ fruits: ["New York", "Chicago", "London"] })
}
</script>
In this line <FruitPrices fruit="item"></FruitPrices> you must use : before the fruit prop, because you are not passing a string, but a kind of variable,
<FruitPrices :fruit="item"></FruitPrices>
And you are ready to show the fruit's price in each city!
By the way, related to your question about v-if, using v-if with v-for is not a good way to filter, the best way to do it is using computed properties.
<FruitPrices fruit:"item" v-for="(item,index) in fruits" :key="index">
</FruitPrices>
you can use v-for in component tag

How to bind whole component in Vue.js?

I just want to ask how to bind whole component (for example div) in Vue.js. Is there anything like innerHTML? Here is example:
Parent.vue
<template>
<div id="parent">
//some elements
</div>
</template>
Child.vue
<template>
<div id="child">
//some different elements
</div>
</template>
Now how to innerHTML child in parent? I've tried something like v-html:component and then data(){ return{ component: and here I dont know how to pass whole vue Component like Child.vue div. Should I use refs or something?
Now I use visibility attribute from css and I change it but I don't think that is good way to do this.
If you want to switch between components, then check out VueJS dynamic components:
https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
You can use the component element and the :is prop to send down what component to render.
I have a working demo here: https://codepen.io/bergur/pen/bPEJdB
Imagine the following simple Vue component
Vue.component('menuList', {
data() {
return {
list: ['Menu item A', 'Menu item B']
}
},
template: `
<ul>
<li v-for="item in list">{{ item}}</li>
</ul>
`
})
This is a simple component rendering a unordered list of menu items. Lets create another similiar component that renders ordered list of products. Note that just to make them a little different, the menuList that has ul and the productList has ol
Vue.component('productList', {
data() {
return {
list: ['Product item A', 'Product item B']
}
},
template: `
<ol>
<li v-for="item in list">{{ item}}</li>
</ol>
`
})
Now we can create a main VueJS that renders these components depending on which button I press. You can have what ever trigger/action you want to change the component.
new Vue({
name: 'main',
el: '#main',
data() {
return {
header: 'Component switching',
selectedComponent: 'menuList'
}
},
methods: {
setComponent(name) {
this.selectedComponent = name
}
},
template: `<div>
<button #click="setComponent('menuList')">Menu List</button>
<button #click="setComponent('productList')">Products</button>
<component :is="selectedComponent" />
</div>`
})
So here the magic begins.
We create a app with some data properties. The header property is just a string value, and selectedComponent tells us which component is beeing rendered.
In our template we use the <component :is="selectedComponent /> so initially the menuList component is the active one.
We create a method called setComponent that takes in a string value and sets that as a new value for selectedComponent. By pressing a button a new value for selectedComponent is set and the component is rendered. Voila

How to add/extend an object in a component

In vue.js, you can iterate over an array of items in your template like so:
<div id="app">
<div v-for="(item, i) in items">i: item</div>
</div>
<script>
var example2 = new Vue({
el: '#app',
data: {
items: ['one', 'two', 'three']
}
})
</script>
Through experimentation, I also discovered you can do something similar with an object instead of an array:
<div id="app">
<div v-for="(item, i) in items">i: item</div>
</div>
<script>
var example2 = new Vue({
el: '#app',
data: {
items: {one: 'one', two: 'two', three: 'three'}
}
})
</script>
If you want to add to the array, you can do something like example2.items.push('four'), and vue.js will react by inserting another DOM element. However, how would you go about inserting another item into an object instead in such a way that vue.js will react the same as it did to the array? You can't use the push method because it's not available to a generic object, so
I'm left trying something like:
example2.items.four = 'four'
But vue.js doesn't detect that change, so no new element is inserted into the DOM. My question is: How can i insert a new object ?
You have to use set like this:
this.$set(this.myObject, 'newKey', { cool: 'its my new object' })
You could use Object.assign too:
let newObject = { newKey: { cool: 'its my new object' }}
this.myObject = Object.assign({}, this.myObject, newObject)
More: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
Well, I asked too soon. I found the following documentation that answers my question. "Object Change Detection Caveats," found here: https://v2.vuejs.org/v2/guide/list.html

VueJS - Component inside of v-for

I am trying to render a list of objects from my Vue-Instance. Each object should use a component, so I put the component into the v-for-loop. But all I get is list.title and list.text instead of the correct values.
Is there a special way to use components in v-for-loops?
I found this thread in the Vue-Forum, but don't know how to use it or if it's the right way.
App:
<div id="app">
<div v-for="list in lists">
<listcard title="list.title" text="list.text"></listcard>
</div>
</div>
Template:
<template id="listcard-template">
<div class="card">
<h2>{{ title }}</h2>
<p>{{ text }}</p>
</div>
</template>
My component:
Vue.component('listcard', {
template: '#listcard-template',
props: ['title', 'text']
})
Vue-Instance:
new Vue({
el: "#app",
data: {
lists: [
{title: "title1", text: "text1"},
{title: "title2", text: "text2"},
...
]
}
})
Thanks!
You should pass then as dynamic prop using : in front of parameters:
<listcard :title=list.title :text=list.text></listcard>
From documentation:
A common mistake beginners tend to make is attempting to pass down a number using the literal syntax:
<!-- this passes down a plain string "1" -->
<comp some-prop="1"></comp>
However, since this is a literal prop, its value is passed down as a plain string "1", instead of an actual number. If we want to pass down an actual JavaScript number, we need to use the dynamic syntax to make its value be evaluated as a JavaScript expression:
<!-- this passes down an actual number -->
<comp :some-prop="1"></comp>
https://vuejs.org/guide/components.html#Literal-vs-Dynamic

Categories