[Vue] Slots - 2. Scoped Slots
Introduction
Slots allow developers to have the flexibility to provide content to a child component, but what happens when the child component has control of the data? In these cases, scoped slots are here to come to the rescue.
What are scoped slots?
Scoped slots is a technique for allowing a component to expose data to the slot’s template block. While that may sound like a mouthful, if we take the analogy of a crane game, slots allow us to put data into it from the parent.
However, when we want to access data from the child, standard slots will not give us permission to grab that data. As a result, we need to expose the data from the child so that we can grab it and put it in the slot as desired.
In terms of code, let’s take a look at a Library.vue
and Book.vue
example:
📄Book.vue
<template>
<div class="book">
<slot name="title"></slot>
</div>
</template>
<script>
export default {
data() {
return {
bookTitle: 'Child Providing Data'
}
}
}
</script>
📄Library.vue
<template>
<Book>
<template v-slot:title>
<!-- How do we get the bookTitle from Book.vue? -->
</template>
</Book>
</template>
As we can see in the code example, the data is currently embedded in our Book.vue
component and we need to some how render it in the Library.vue
component. Let’s learn how we can use scoped slots!
How do you use scoped slots?
To learn how to use scoped slots, let’s start with a standard named slot with a name of header
.
📄LogoHeader.vue
<template>
<slot name="header" />
</template>
<script>
export default {
data() {
return {
logoImage: '/images/logo.png'
}
}
}
</script>
In this scenario, let’s say that we want to expose our logoImage
property to the slot. To do this, we define a prop (like you would with a normal component) on our slot.
<template>
<slot name="header" :logo="logoImage" />
</template>
<script>
export default {
data() {
return {
logoImage: '/images/logo.png'
}
}
}
</script>
By defining these “slot props,” this allows us to access them in the <template>
block by exposing the slotProps
value.
Once we expose the slot props, this allows us to use it within that <template>
block.
Using our code from this scenario, it would result in the following:
📄LogoHeader.vue
<template>
<slot name="header" :logo="logoImage" />
</template>
<script>
export default {
data() {
return {
logoImage: '/images/logo.png'
}
}
}
</script>
📄App.vue
<template>
<LogoHeader>
<template v-slot:header="slotProps">
{{ slotProps.logo }}
</template>
</LogoHeader>
</template>
Why it is called “scoped” slots
Let’s start by looking at our child component:
📄Book.vue
<template>
<div class="book">
<slot name="title" :bookTitle="bookTitle" />
</div>
</template>
<script>
export default {
data() {
return {
bookTitle: 'Child Providing Data'
}
}
}
</script>
It provides the bookTitle
data to the title
slot, which we then access in our parent component:
📄Library.vue
<template>
<Book>
<template v-slot:title="slotProps">
<h1>{{ slotProps.bookTitle }}</h1>
</template>
</Book>
</template>
With the bookTitle
property exposed via slotProps
, a common misconception is that it is now available to the parent component for use in things like methods, computed properties, or something else.
🛑 So in the event we wanted to create an uppercaseTitle
in our Library.vue
component, we might try to write code like this:
<script>
export default {
computed: {
uppercaseTitle() {
// 🛑THIS DOES NOT WORK
this.slotProps.bookTitle.toUpperCase()
}
}
}
</script>
But this will not work, because the data that the child is exposing to the parent is only scoped to the slot template block.
While this may seem like an inconvenience, this ultimately helps developers by encouraging us to keep the concerns for each component in the correct place. In other words, rather than try to create a computed property in our Library.vue
component, it would be more consistent to keep that logic inside of Book.vue
instead and then expose it via slot props.
📄Book.vue
<template>
<div class="book">
<slot name="title"
:bookTitle="bookTitle"
:uppercaseBookTitle="uppercaseTitle"
/>
</div>
</template>
<script>
export default {
data() {
return {
bookTitle: 'Child Providing Data'
}
},
computed: {
uppercaseTitle() {
return this.bookTitle.toUpperCase()
}
}
}
</script>
So to reinforce the concept once more, any data exposed via slot props is scoped to the slot template and nothing more.
Destructuring slot props
When using slot props, it’s a common desire for developers to find ways to make their code more concise, in other words, less code. And because slotProps
results in a JavaScript object, we can use ES6 destructuring to make our code a little easier to read.
📄Library.vue (before)
<template>
<Book>
<template v-slot:title="slotProps">
<h1>{{ slotProps.bookTitle }}</h1>
</template>
</Book>
</template>
📄Library.vue (after)
<template>
<Book>
<template v-slot:title="{ bookTitle }">
<h1>{{ bookTitle }}</h1>
</template>
</Book>
</template>