Table of contents
Introduction
Recently, I started learning VueJS, and this article is part of the series of my notes while learning it. In this post, I am covering two, bit more advanced topics of the slots. One of them is conditional slots. I called it conditional, but that is not the best way to call it. What I mean here is checking if the slot template is defined. The second one is scoped slots. Scoped slots are important because sometimes you want to pass some data to the slot. For that, you need to use scoped slots.
Initial component
A great example we can use for scoped and conditional slots is a list. Lists are quite a common requirement in UI development. Depending on the use case, we would render different list items differently. Let’s start by defining this starter list component that will in its basic version just accept the list of items we want to render.
<template>
<ul>
<li v-for="item in listItems" :key="item.id">List item</li>
</ul>
</template>
<script>
export default {
props: ["items"],
computed: {
listItems() {
return this.$props.items;
}
},
data() {
return {}
}
}
</script>
Items I am using in this example are quite simple, they contain the id, title, and description property. As I progress in this post, I will use all of them.
Conditional slots
In our list, I want to display both the description and the title, so we can update our template as follows.
<template>
<ul>
<li v-for="item in listItems" :key="item.id">
<div>{{item.title}}</div>
<div>{{item.description}}</div>
</li>
</ul>
</template>
This would display wanted data, but quite often there are requirements to display this data differently. Sometimes just styling, other times, maybe something more, and even sometimes different for each use case. And as we already saw previously, slots are a great way to achieve that.
<template>
<ul>
<li v-for="item in listItems" :key="item.id">
<div>
<slot name="title"></slot>
</div>
<div>
<slot name="default"></slot>
</div>
</li>
</ul>
</template>
We could use this by adding slot templates to the usage of our list component.
<template>
<div class="about">
<h1>Slots advanced example</h1>
<ListWithSlots :items="items" >
<template #title>title</template>
<template #default>description</template>
</ListWithSlots>
</div>
</template>
This is perfectly fine, but what if we don’t want to pass slot templates every time? In that case, nothing would be displayed. Which is not what we want. For such a situation, we would check if the slot is defined, and in case it is not, use our default version that just displays the data.
So far, we could have seen how this keyword gives us access to many useful things. One of them is access to the slots. We can use it in the script to check if some slot template is defined by using this.$slots or in the template by just using the $slots. Then in combination with the v-if directive, we either show the default template or the slot template.
<template>
<ul>
<li v-for="item in listItems" :key="item.id">
<div>
<slot v-if="$slots.title" name="title"></slot>
<div v-else>{{item.title}}</div>
</div>
<div>
<slot v-if="$slots.default" name="default"></slot>
<div v-else>{{item.description}}</div>
</div>
</li>
</ul>
</template>
Scoped slots
In the example above, when I used slots, maybe you noticed that I hardcoded strings, and didn’t use the values. This is because slots are not able to access the scope of the component, therefore they are not able to access the data. But there is a way to pass data to the slot component. And those are the scoped slots. To use this we need to do two things. First, in the component, when using the slot, we need to pass all the props we are going to use. Then, we need to update templates for slots and define the value for the slot name directive.
<template>
<ul>
<li v-for="item in listItems" :key="item.id">
<div>
<slot v-if="$slots.title" name="title" :title="item.title"></slot>
<div v-else>{{item.title}}</div>
</div>
<div>
<slot v-if="$slots.default" name="default" :description="item.description"></slot>
<div v-else>{{item.description}}</div>
</div>
</li>
</ul>
</template>
Now that data is passed, we can use this data in the slot template. To do it we just need to give some value to the v-slot directive, and this value is the name we are going to use to access passed values.
<template>
<div class="about">
<h1>Slots advanced example</h1>
<ListWithSlots :items="items" >
<template #title="titleProps">{{ titleProps.title}}</template>
<template #default="defaultProps">{{ defaultProps.description }}</template>
</ListWithSlots>
<ListWithSlots :items="items" />
</div>
</template>
One thing to note is that when using the scoped slots, you don’t need to define a name to access values, you can use destructor immediately and access the values that way.
The code used in this article can be found in my GitHub repository.
For more, you can follow me on Twitter, LinkedIn, GitHub, or Instagram.