You know how to pass data into a slot by using scoped slots, but how do you communicate back?
You pass a method into our slot and then call that method in the slot. You can't emit an event, because slots share the same context (or scope) as the parent component.
// Parent.vue<template><Child><template #default="{ clicked }"><button @click="clicked">Click this button</button></template></Child></template>
// Child.vue<template><div><!-- Pass `handleClick` as `clicked` into our slot --><slot :clicked="handleClick" /></div></template>
In this article we'll cover why this works, as well as:
Let's take a look at just the Parent
component for now:
// Parent.vue<template><Child><button @click="">Click this button</button></Child></template>
We have a button
inside of the slot of the Child
component. When that button
is clicked, we want to call a method inside of the Parent
component.
If the button
wasn't in the slot, but instead a child of the Parent
component directly, we have access to the method on the component:
// Parent.vue<template><button @click="handleClick">Click this button</button></template>
The same is true for when that button
component is inside of a slot:
// Parent.vue<template><Child><button @click="handleClick">Click this button</button></Child></template>
This works because the slot shares the same scope as the Parent
component.
I've talked about scope in Vue components before, but this is a new type of scope that I haven't talked about.
(I'll call this one "template scope", and I'll need to do a follow-up to that article at some point!)
This is what template scope is: everything inside of a template has access to everything defined on a component.
This includes all elements, all slots, and all scoped slots.
So no matter where that button
is located in the template, it has access to the handleClick
method.
This may seem weird at first, and it's one of the reasons why slots are tricky to understand. The slot ends up being rendered as a child of our Child
component, but it doesn't share scope with the Child
component. Instead, it acts as if it's a child of the Parent
component.
I explore this idea — that slots pretend to be something they're not — more in this article.
If we want to emit from the slot to the grandparent component, we use the regular $emit
method:
// Parent.vue<template><Child><button @click="$emit('click')">Click this button</button></Child></template>
Because the slot shares the same template scope as the Parent
component, calling $emit
here will emit an event from the Parent
component.
What about communicating back to the Child
component?
We just saw that calling $emit
in the slot will emit an event from the Parent
to our Grandparent
component, so that's ruled out.
But we know how to pass data into the slot from the child:
// Child.vue<template><div><slot :data="data" /></div></template>
And how to use it within the scoped slot:
// Parent.vue<template><Child><template #default="{ data }">{{ data }}</template></Child></template>
Instead of just passing data, we can also pass methods into the scoped slot. If we hook up those methods in the right way, we can use that to communicate back to the Child
component:
// Parent.vue<template><Child><template #default="{ clicked }"><button @click="clicked">Click this button</button></template></Child></template>
// Child.vue<template><div><!-- Pass `handleClick` as `clicked` into our slot --><slot :clicked="handleClick" /></div></template>
Whenever the button
is clicked, the handleClick
method from the Child
component is called.
If you're interested in learning more about these kinds of patterns, I have a course on Reusable Components that covers slots in extreme detail.