How to make a component use v-for have dynamic slots - vue.js

I have a child component that uses v-for. I want to be able to have the parent pass down a slot, or something similar of how it wants each item in the v-for display. However, the problem is that the parent does not have access to each individual item in the v-for as it's rendered.
Some things i've tried is passing a slot with specific keys. e.g.
<child-comp :items="items">
<div v-text="item.text" slot="body"/>
</child-comp>
Basic code may look like this for what i'm trying (though it doesn't work)
Parent component would look something like
<template>
<child-comp :items="items>
<div v-text="item.text"
</child-comp>
</template>
items = [{ text: 'hello'}]
Child would look something like this
<template>
<div>
<span v-for="item in items">
<slot></slot>
</span>
</div>
</template>
Note this has to be dynamic because one item might do v-text, another may do something like add more html such as an image, and another may do something completely different.

I believe you're looking for scoped slots.
https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
Note that the preferred syntax for using scoped slots changed in Vue 2.6.0 so the exact way you write this will depend on which version you're using.
Your child would pass the item to the slot, like this:
<template>
<div>
<span v-for="item in items">
<slot :item="item"></slot>
</span>
</div>
</template>
The parent would look like this for Vue 2.6.0+:
<template>
<child-comp :items="items">
<template v-slot:default="slotProps">
<!-- The parent can put whatever it needs here -->
{{ slotProps.item }}
</template>
</template>
</child-comp>
</template>
Any props passed to the slot by the child will be included in the slotProps. There's no need to call it slotProps and in practice it is usually destructured (see the docs for more details).
For Vue 2.5 you'd use slot-scope instead:
<template>
<child-comp :items="items">
<template slot-scope="slotProps">
<!-- The parent can put whatever it needs here -->
{{ slotProps.item }}
</template>
</template>
</child-comp>
</template>
Prior to Vue 2.5.0 slot-scope was called scope.

Related

Components as slot content Vue3

I wonder how to add components as slot content. Instead of doing it like:
<template v-slot:content>CONTENT HERE</template>
I'd like to pass a component.
PkDynModalCenter:
<div class="...">
<div id="modalIndicator" class="...">
<slot name="content"></slot>
</div>
</div>
Parent:
<PkDynModalCenter v-if="togglePayment" #closeModal="togglePayment = false">
<PaymentTemplate :info="voucherInfo" name="content"></PaymentTemplate>
</PkDynModalCenter>
But it's not displayed. Docs are very vague there...
Have a look at the docs for named slots: https://vuejs.org/guide/components/slots.html
You can only use your parent code with the default slot, so your PkDynModalCenter should look like this:
<div class="...">
<div id="modalIndicator" class="...">
<slot></slot> <!-- no name → default slot -->
</div>
</div>
With your current code for the modal, you can only call it like you did in your first code snippet.

Passing slots through from Parent to Child Components

I have built a user-defined component (async-select) on top of another component (vue mutliselect) like this:
https://jsfiddle.net/2x7n4rL6/4/
Since the original vue-multiselect component offers a couple of slots, I don't want to loose the chance to use them. So my goal is to make these slots available from inside my custom component. In other words, I want to something like this:
https://jsfiddle.net/2x7n4rL6/3/
But that code oes not work.
However, if I add the slot to the child component itself, it works just fine (which you can see from the fact that options become red-colored).
https://jsfiddle.net/2x7n4rL6/1/
After surfing the web, I have come across this article, but it does not seem to work
Is there any way in VueJS to accomplish this ?
Slots can be confusing!
First, you need a template element to define the slot content:
<async-select :value="value" :options="options">
<template v-slot:option-tmpl="{ props }">
<div class="ui grid">
<div style="color: red">{{ props.option.name }}</div>
</div>
</template>
</async-select>
Then, in the parent component, you need a slot element. That slot element itself can be inside of another template element, so its contents can be put in a slot of its own parent.
<multiselect
label="name"
ref="multiselect"
v-model="localValue"
placeholder="My component"
:options="options"
:multiple="false"
:taggable="false">
<template slot="option" slot-scope="props">
<slot name="option-tmpl" :props="props"></slot>
</template>
</multiselect>
Working Fiddle: https://jsfiddle.net/thebluenile/ph0s1jda/

control over inherited attributes by vue component

Is there a way to have control over attributes provided through the component tag?
For example:
<my-component class="myClass" style="myStyle"></my-component>
My component:
<template>
<div>
<div>
</div>
<div>
</div>
</div>
</template>
At render Vue applies given attributes on the root:
<div class="myClass" style="myStyle">
<div>
</div>
<div>
</div>
</div>
I want to control where those attributes are applied like so:
<div>
<div>
</div>
<div class="myClass" style="myStyle">
</div>
</div>
#Boussadjra Brahim answer is definitely one way to handle it, however this will require you to pass in all of the class attributes you want everytime you define the component.
This question is answered in this SO post already as well.How to style a nested component from its parent component in Vuejs?
If you want a bit more flexibility I would suggested using interpolation and properties as below. This will let you define some default classes and pass in whatever else in addition.
<app-header :headerclass="parent-header-class"> </app-header>
Inside of your child component, you can use these properties and v-bind the class inside the HTML, as shown in the example below:
<template>
<div :class=`${headerClass} internal-class-example button`> </div>
</template>
Note: This does not allow you to use any scoped parent CSS to pass to the child. The classes you pass down must be global. Otherwise, the child component will not know what it is.

Vuejs nested slots: how to pass slot to grandchild

I use different vuetify components, for example v-menu. It has a template like this:
<v-menu>
<a slot="activator">menu</a>
<v-list>
<v-list-tile>Menu Entry 1</v-list-tile>
<v-list-tile>Menu Entry 2</v-list-tile>
</v-list>
</v-menu>
Suppose I want to add another wrapper around it. That is my special menu component that has some predefined menu options. And I want it to has an activator slot as well. And the last should be somehow assigned to the original v-menu activator slot. Is it possible?
Example:
// index.vue:
<template>
<my-special-menu>
<button>My special menu trigger</button>
</my-special-menu>
</template>
// MySpecialMenu.vue
<template>
<v-menu>
<slot slot="activator"/> <-- I don't know how to write this line
<v-list>...</v-list>
</v-menu>
</template>
<slot slot="activator"> is an incorrect equation. The goal is to pull the content from the parent (that is <button>..</button> in the example), and use it as slot="activator" in v-menu.
I can write it like this:
<v-menu>
<a slot="activator"><slot/></a>
...
</v-menu>
But this case the result template will be:
<div class="v-menu__activator">
<a>
<button>My special menu trigger</button>
</a>
</div>
That's not exactly what I want. Is it possible to get rid off <a> wrapper here?
Update:
We can use a construction like <template slot="activator"><slot name="activator"/></template> to throw some slot to a grand child. But what if we have multiple slots and we want to proxy them all? That's like inheritAttrs and v-bind="$attrs" for slots. Is it currently possible?
For example, there's <v-autocomplete> component in vuetify that has append, prepend, label, no-data, progress, item, selection etc slots. I write some wrapper component around this, it currently looks like:
<template>
<v-autocomplete ..>
<template slot="append"><slot name="append"/></template>
<template slot="prepend"><slot name="prepend"/></template>
<template slot="label"><slot name="label"/></template>
...
<template slot="item" slot-scope="props"><slot name="item" v-bind="props"/></template>
</v-autocomplete>
</template>
Is it possible to avoid all slots enumeration here?
If you put the slot attribute on a html element, that html element is passed to the child component to fill the slot with that name. If you don't want to pass along a html element, you can use slot on a template tag within your component. A template tag groups elements, but does not render to a html element, which is perfect here. You can use template tags also for other things, such as to group elements in a v-if for example, or to repeat multiple elements with a v-for.
// App.vue
<template>
<div id="app">
<test>
<template slot="activator">
Click <b>me</b>!
</template>
</test>
</div>
</template>
// Test.vue
<template>
<div class="wrapper">
<grand-child>
<template slot="activator">
<slot name="activator"></slot>
</template>
</grand-child>
This is some text
</div>
</template>
// GrandChild.vue
<template>
<div>
<a href="#" #click="toggle = !toggle">
<slot name="activator">Default</slot>
</a>
<div v-if="toggle">This appears and disappears</div>
</div>
</template>
Edit: If you want to do this for arbitrary slots, this is also possible. this.$slots contains the slots and their content, so with something like the following, you can pass the slot content to a slot with the same name:
<grand-child>
<template v-for="(_, slot) in $slots">
<template :slot="slot">
<slot :name="slot"></slot>
</template>
</template>
</grand-child>
For completeness sake, scoped slots can be accessed through $scopedSlots and be propagated like so:
<grand-child>
<template v-for="(_, slot) in $scopedSlots" v-slot:[slot]="props">
<slot :name="slot" v-bind="props" />
</template>
</grand-child>
source and comment
I had EsLint errors because of the depreciated :slot and $scopedSlots attributes.
So I combined both of #Sumurai8 answers like this and it works great:
<template v-for="(_, slot) in $slots" v-slot:[slot]>
<slot :name="slot"></slot>
</template>
If you have both named and unnamed slots with props:
Vue 3
<template v-for="(_, name) in $slots" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>
Typescript version
<template v-for="(_, name) in ($slots as {})" #[name]="slotData">
<slot :name="name" v-bind="slotData || {}" />
</template>

How do I use the same slot-element for all elements?

So if I have a slot like this:
<span slot="?" slot-scope="data>...</span>
What should question mark be here if I want to use this for everything?
This is not supported by the current state of Vue, nor is this supported when trying to hack this feature in using the JavaScript Proxy class, this is because the internal design of Vue first collects all children and maps them to an object, before passing this to the next component.
You can work around this by specifying your slot contents multiple times, like:
<!-- inside parent -->
<my-child>
<p slot="head">Hello World</p>
<p slot="body">Hello World</p>
</my-child>
Or modifying the child to accept a base slot to use if a slot is not passed in
<!-- inside child -->
<div>
<slot name="head">
<slot name="base/>
</slot>
<slot name="body">
<slot name="base/>
</slot>
</div>