Home > Software design >  Is it possible to pass v-for props to a component slotted in the html? Slot props aren't workin
Is it possible to pass v-for props to a component slotted in the html? Slot props aren't workin

Time:11-06

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.

  • Related