Daniel Elkington
Daniel Elkington

Reputation: 3637

How to declare a list of slots with dynamic names and dynamic components in a VueJS template?

Suppose I have a component with data:

data: function () {
  return {
    slots: [
      { Id: 1, slotName: 'apple', componentName: 'Apple' },
      { Id: 2, slotName: 'banana', componentName: 'Banana' }
    ]
  }
}

and I want to pass the list of slots as scoped slots to a child component. The following syntax won't work because you cannot use v-for on a template element:

<child-component>
    <template v-for="slot in slots" :key="slot.Id" v-slot[slot.slotName]="slotProps">
        <component :is="slot.componentName" :someProp="slotProps"></component>
    </template>
</child-component>

and the following syntax won't work because in Vue 2.6.0+ any content not wrapped in a <template> using v-slot is assumed to be for the default slot.

<child-component>
    <component v-for="slot in slots" :key="slot.Id"
        :is="slot.componentName" v-slot[slot.slotName]="slotProps" :someProp="slotProps"></component>
</child-component>

The following will work but the compiler throws a warning and it uses deprecated syntax:

<child-component>
    <component v-for="slot in slots" :key="slot.Id"
        :is="slot.componentName" :slot="slot.slotName" slot-scope="slotProps" :someProp="slotProps"></component>
</child-component>

Is there any way to achieve this using non-deprecated Vue template syntax and without using a render function?

Upvotes: 33

Views: 42731

Answers (3)

Despertaweb
Despertaweb

Reputation: 1820

To me, what was really revealling was adding the Dynamic Slot name.

As @Ezequiel Fernandez stated above you could achieve it by:

<template  v-slot:[slotName]="slotProps">
  <!-- Add Here the content -->
</template>

Here's my example of how to reverse columns where each column was a slot:

<template>
  <RowQCardSection subtitle="Subsets">
    <template v-slot:[leftCol]>
           Label: {{label}}
    </template>

    <template v-slot:[rightCol]>
      <SelectSubsets />
    </template>
  </RowQCardSection>
</template>

<script setup>
import RowQCardSection from 'src/components/updateTerm/RowQCardSection.vue'
import SelectSubsets from 'src/components/SelectSubsets.vue'
import { computed } from 'vue'

const props = defineProps({
  label: String

  reverseColumns: {
    default: false,
  },
})

const leftCol = computed(() => {
  return props.reverseColumns ? 'right' : 'left'
})

const rightCol = computed(() => {
  return props.reverseColumns ? 'left' : 'right'
})
</script>

Upvotes: 2

Ezequiel Fernandez
Ezequiel Fernandez

Reputation: 1074

You can use v-for into a template component

You just need to specify the key on the component.

<child-component>
  <template v-for="slot in slots" v-slot:[slot.slotName]="slotProps">
    <component :key="slot.Id" :someProp="slotProps"></component>
  </template>
</child-component>

Upvotes: 37

Victor Weiss
Victor Weiss

Reputation: 476

I had to generate the slot name dynamically. Based on @EzequielFernandez's answer, I did it using a function :

<template v-for="locale in locales" v-slot:[getSlotName(locale)]="slotProps">
 ...
</template>
data() {
  return {
    locales: ['en', 'fr']
  }
},
methods: {
  getSlotName(locale) {
    return `locale-${locale}`
  }
}

Upvotes: 10

Related Questions