I would like to pass the "instance" property of the v-for loop to the slot, and use it in a component added to that slot in the html.
List Component
<template>
<two-col-row-display
:field="field"
:fieldcss="fieldcss"
:valuecss="valuecss"
>
<component
:is="listType"
>
<li v-for="instance, i in map_instances(instances)" :key="map_id(instance)">
<slot name="default" :instance="instance">{{ map_display(instance) }}</slot>
</li>
</component>
</two-col-row-display>
</template>
Slotted Component
<template v-slot:default="liProps">
<div class="vue_wrap">
{{ liProps.instance.description }}
</div>
</template>
HTML
<display-list
field="Display Ingredient"
:map_id="instance => instance.ingredient_id"
:map_instances="instances => {{ $recipe->ingredients }}"
>
<display-ingredient></display-ingredient>
</display-list>
The "instance" property is not passed via "liProps" to the component. I get a "prop not defined" error whether I declare it as a prop or not. If create a new component and pass in both the display list and the slotted component the desired result is achieved, but this is less flexible. I would prefer to find a way to access the v-for loop data in the component as slotted into the html.
Works, but not desirable for reusability
<template>
<display-list
:field="field"
:fieldcss="fieldcss"
:list-type="listType"
:map_id="map_id"
:map_instances="map_instances"
:valuecss="valuecss"
>
<template v-slot:default="liProps">
{{liProps.instance.description}}
</template>
</display-list>
</template>
CodePudding user response:
<display-list>
<display-ingredient></display-ingredient>
</display-list>
In the above (simplified) template, you aren't passing any props nor slots to <display-ingredient>
; it won't magically receive this data just because you slotted it inside <display-list>
.
You should make <display-ingredient>
receive an instance
prop. It doesn't need a slot since it just has to render instance
:
<template>
<div class="vue_wrap">
{{ instance.description }}
</div>
</template>
Now you have to hook together the components: pass instance
to <display-ingredient>
. instance
is passed to the default slot of <display-list>
, so you can just use v-slot
shorthand syntax for the special case when the component only has one slot which is default
:
<display-list v-slot="{ instance }">
<display-ingredient :instance="instance"></display-ingredient>
</display-list>
CodePudding user response:
After a lot more research and experimentation I found that substituting a for the and using dynamic components
DisplayListDynamic
<template>
<two-col-row-display
:field="field"
:fieldcss="fieldcss"
:valuecss="valuecss"
>
<component :is="listType">
<li v-for="instance, i in map_instances(instances)" :key="map_id(instance)">
<component :is="display || 'div'" :instance="display ? instance : undefined" class="vue_wrap">
{{ map_display(instance) }}
</component>
</li>
</component>
</two-col-row-display>
</template>
...
props: {
display: { //name of the component, if provided
type: String,
},
map_display: {
type: Function,
default: () => '-', // This keeps from an error when the slot default isn't used
},
...
The :is="display || 'div'"
changes the component to the Vue component set in display. If it is not set, it becomes a div when rendered.
The :instance="display ? instance : undefined"
sets the instance prop of the component to "instance" only if display is set, otherwise it is undefined and does not render in the view. This prevents instance=[object object]
from appearing in the rendered html if the default div is being used.
{{ map_display(instance) }}
is the default value, if the display prop is not declared.
if map_display is not declared, default: () => '-'
the prop defaults to showing a character
DisplayIngredient
<template>
<div>
{{ instance.description }}
</div>
</template>
...
props: {
instance: {
type: Object,
required: true,
},
},
The instance prop is declared and required in the component that can be swapped in
html
<display-list-dynamic
field="Ingredients Dynamic"
:map_display="instance => instance.line"
:map_id="instance => instance.ingredient_id"
:map_instances="instances => {{ $recipe->ingredients }}"
></display-list-dynamic>
<display-list-dynamic
field="Ingredients Dynamic Switch"
display="display-ingredient"
:map_id="instance => instance.ingredient_id"
:map_instances="instances => {{ $recipe->ingredients }}"
></display-list-dynamic>
In the first example, the display prop is not declared. :map_display="instance => instance.line"
a default value would be rendered if the map_display prop is not defined. In the second example the display prop is declared display="display-ingredient"
and thus the component becomes the "display-ingredient" Vue component.