Reputation: 972
I have a grandparent component, a parent component, and a child component.
Question: If the grandparent passes a component to a slot in the child, can the parent insert data so it shows up in the child?
Visually it looks like this:
--------Grandparent -------------> Parent -------------> child------------>Slot
/|\ /|\ /|\
| | |
[Slot component identified ] | [Data added]| [data shows in slot component]
It should be noted that the parent is a list, and the children are list items. The children do receive the slot, but I can't get the parent to pass the individual ids to the children.
The actual templates look like this:
Grand parent (list container):
<template>
...
<KeywordList v-model="activeKeywordIds">
<KeywordDetailsEditContent slot="details" v-if="activity === 'editResumeContent'"/>
<template v-slot:details>
<KeywordDetails_Editing v-if="editing"/>
<KeywordDetails_PrivView v-if="viewing && loggedIn"/>
<KeywordDetails_PublicView v-else />
</template>
</KeywordList>
...
</template>
Parent (KeywordList):
<template>
...
<KeywordLI
v-for="keywordId of value"
:key="keywordId"
:str-keyword-id="keywordId"
>
<template v-slot:details>
<slot name="details"></slot>
</template>
</KeywordLI>
...
</template>
Child (KeywordLI):
<template>
...
<div
class="detailsContainer"
ref="detailsContainer"
v-if="appWidthDescription > 1"
>
<div ref="details" style="height: fit-content">
<slot name="details"></slot>
</div>
</div>
..
</template>
Slot component
<template>
{{strKeywordId}} [Other computed/fetched details depending on which component was sent]
</template>
So the grandparent knows what details render, but the details aren't broken out until the parent component, and the child component is where they need to come together.
So far this hasn't worked:
<template>
...
<KeywordList>
...
<KeywordLI
v-for="keywordId of value"
:key="keywordId"
:str-keyword-id="keywordId"
>
<template v-slot:details>
<slot name="details" :str-keyword-id='keywordId'></slot> //<----Adding prop to slot
</template>
</KeywordLI>
...
</KeywordList>
...
</template>
<template>
...
<KeywordLI
v-for="keywordId of value"
:key="keywordId"
:str-keyword-id="keywordId"
>
<template v-slot:details :str-keyword-id='keywordId'> //<----Adding prop directly to slot
<slot name="details"></slot>
</template>
</KeywordLI>
...
</template>
Upvotes: 2
Views: 1140
Reputation: 29092
Passing content into a normal slot defines a single piece of content. Within the receiving component you can only display it once, using <slot>
. The content is already created before it is passed in, so there's not much the receiving component can do with the slot's contents other than displaying it somewhere.
In your case you need the slot's contents to be repeated several times within a loop. Further, you need each looped item to render slightly differently.
Vue has something to handle this scneario: scoped slots.
Official documentation: https://v2.vuejs.org/v2/guide/components-slots.html#Scoped-Slots
There are various ways to think of scoped slots. You might like to think of them as mini-templates that you can pass down to your descendant components. Another way to think of them is as callback functions, allowing descendants to pass data back up the chain.
There are many possible uses for scoped slots but templating lists is one of the most common examples.
Let's start with your grandparent component:
<template>
<KeywordList v-model="activeKeywordIds">
<template v-slot:details="{ keywordId }">
<KeywordDetailsEditContent
v-if="activity === 'editResumeContent'"
:str-keyword-id="keywordId"
/>
<KeywordDetails_Editing
v-if="editing"
:str-keyword-id="keywordId"
/>
<KeywordDetails_PrivView
v-if="viewing && loggedIn"
:str-keyword-id="keywordId"
/>
<KeywordDetails_PublicView
v-else
:str-keyword-id="keywordId"
/>
</template>
</KeywordList>
</template>
A few notes on this:
="{ keywordId }"
part we're now in scoped slot territory.str-keyword-id
will need to be defined as a prop for each of these 4 components.is
instead. https://v2.vuejs.org/v2/api/#isv-slot
syntax with the Vue 2.5 slot="details"
syntax. I've merged them to use just the former.Of course the big question is where does the keywordId
come from?
The v-slot:details="{ keywordId }"
is receiving data passed to the scoped slot. You can think of it as being roughly analogous to a callback function that looks like this:
function details ({ keywordId }) {
// ...
}
Here the { keywordId }
part is just object destructuring of the function's first argument.
Now we need to 'call' this scoped slot and pass it the correct argument. Generally that would look something like this:
<slot name="details" :keywordId="blah" />
All attributes apart from name
will be wrapped up in an object and passed as the only argument to the scoped slot 'function'.
This is made more complicated because the question features multiple slots so we need to be careful about exactly what we're passing to where.
So in KeywordList
we would have:
<template v-slot:details>
<slot name="details" :keywordId="keywordId" />
</template>
So the details
slot of KeywordList
is now a scoped slot and passes keywordId
to the slot. However, the similarly named details
slot of the KeywordLI
component is still just a normal slot as it doesn't appear to need to pass anything along.
Upvotes: 1
Reputation: 4464
Correct me if I didn't get it right, but on a very simple example here is what you need:
Working example you have here => codesandbox
Note: I'm not using your components because you will not learn. Try to use what I've prepared and adopt to your needs :-)
Now!
In your Child component you need to prepare a slot
:
<template>
<div>
<h3>Child</h3>
<slot name="details">default Child slot</slot> //default is optional
</div>
</template>
In Paren component you need to put Parent's own slot in a Child slot. Plus :bind what you want (some data
) that your Grandparent needs to use.
<template>
<div>
<h2>Parent</h2>
<Child>
<template v-slot:details>
<slot name="details" :id="3">default Parent slot</slot> // 3 is whatever you need to pass up
</template>
</Child>
</div>
</template>
And then in your Grandparent you use what Parent send through scopedSlot
and pass it all the way down to Child.
<template>
<div>
<h1>Grandparent</h1>
<Parent>
<template v-slot:details="props">
<slot
name="details"
>default Grandparent slot rendered in child komponent with id {{props.id}} send from parent</slot>
</template>
</Parent>
</div>
</template>
Note that this is your Grandparent component that is sending all the information, but it is your Parent who gives him some information.
Hope that helps ;-)
Upvotes: 1