Slot Example Vue
Posted By admin On 07/04/22Vue 2.6 is released with new syntax for Slots using v-slot
directive. In this tutorial, we’re gonna show you:
When Vue 2.6.0 was released, Vue introduced a new unified syntax which is (the v-slot directive) for named and scoped slots. It replaced the slot and slot-scope attributes, which has been deprecated, though not removed, they are still document. For example, if you’re trying to allow custom templates in a container while still retaining access to those containers’ data properties, you’ll want to use a scoped slot. Introduction Scoped slots are a new feature introduced in Vue 2.1.0.
- Syntax to use Vue Slot with
v-slot
directive along with its shorthand - How to use Vue Named Slots with
v-slot
& examples - How to use Vue
v-slot
for Scoped Slots & examples - Vue Dynamic slots example
Related Post: Vue 3 Composition API tutorial with examples
Contents
Vue slots syntax with v-slot directive
With new v-slot
directive, we can:
– combine html layers: component tag and scope of the slot.
– combine the slot and the scoped slot in a single directive.
For example, this is old syntax with slot-scope
:
This is how we combine ListComponent
and template
tag:
And this is old named slots syntax:
Now we use new Vue v-slot
directive:
You can see that:
– We use <template v-slot:header>
to wrap <p>
tag instead of <p slot='header'>
directly. This is because Vue v-slot
can only be used in <component>
or <template>
html tag. It cannot be used in plain HTML tags (<p>
for example).
– We replace slot='content' slot-scope='{data}'
with v-slot:content='{data}'
by combining slot
& slot-scope
. With new Vue v-slot
directive, all slots are compiled into scoped slots. It improves the performance. Why?
Normal slots are rendered during the parent component’s render cycle. So, if any dependency of a slot changes, both the parent and child components will be re-rendered.
When we use scoped slots, slots are compiled into inline functions and called during the child component’s render cycle. This means:
- data from a scoped slot are collected by the child component which is re-rendered separately.
- the changes of parent scope dependency only affect the parent, not the child component. So the child component doesn’t need to update if it uses only scoped slots.
Shorthand for v-slot
#
is the shorthand for Vue v-slot
directive.
For example, #content
stands for v-slot:content
.
The code above can be written as:
Remember that when using shorthand, we must always specify the name of the slot after #
symbol. We cannot use shorthand like this: #='{item}'
.
It must be: #default='{item}'
in which, #default
is the shorthand for v-slot:default
.
In the next parts, we show you some examples that apply new Vue v-slot
directive in practice.
Vue v-slot examples with Named Slots
If we want to use multiple slots in one component, Named Slots are useful.
The code below shows BkrCard
component template with 3 slots:
- header
- title
- default
Remember that <slot>
without name
attribute has the name default
.
Now look at the parent component which use v-slot
directive to specify name for named slots on <template>
tag:
The result will be:
If we pass only one named slot, the default value will be shown:
Vue v-slot example with default slot
In the example above, we use <template v-slot:default>
for the default slot.
We have other ways to specify html code to be considered as default slot also:
– wrap it in a <template>
without Vue v-slot
directive:
– do not wrap it in a <template>
:
The result are the same for 2 cases:
Vue v-slot examples with Scoped Slots
What we should do when we want a child component to allow parent component access its data?
In this example, categories
need to be available to the slot content in the parent. So we bind the categories
as an attribute to the <slot>
element:
The categories
attribute is called slot props.
In the parent scope, Vue v-slot
directive can help us get value of the slot props above:
The result will be:
- Dart
- Flutter
- Vue.js
This is shorthand for v-slot
:
Vue Dynamic slots example
We can use a JavaScript expression in v-slot
directive argument with square brackets:
Now look at the example:
Clicking on the Change button will change the collection
value dynamically.v-slot:[collection]='{categories}'
could become:
v-slot:default='{categories}'
v-slot:new_categories='{categories}'
This is BkrCategories
component with default
and new_categories
slot name:
The result will be:
Conclusion
We’ve learned almost aspects of new Vue v-slot
directive, from v-slot
syntax to its handshort, then apply v-slot
directive on Named Slot examples to Scoped Slots and Dynamic Slots examples.
Happy learning! See you again!
Further reading
Let me ask you about something you've probably never thought about:
Is there a way to populate a parent's slot from a child component?
Recently a coworker asked me this, and the short answer is:
Yes.
But the solution I arrived at is probably very different from what you're thinking right now.
You see, my first approach turned out to be a terrible idea, and it took me a few attempts before I figured out what I think is the best approach to this problem.
It's a thorny Vue architecture problem, but also a very interesting one.
In this article we'll go through each of these solutions one by one, and see why I think they aren't that great. Ultimately we'll land on the best solution at the end.
But why did we have this problem in the first place?
Why this obscure problem?
In our application we have a top bar that contains different buttons, a search bar, and some other controls.
It can be slightly different depending on which page you're on, so we need a way of configuring it on a per page basis.
To do this, we want each page to be able to configure the action bar.
Seems straightforward, but here's the catch:
This top bar (which we call an ActionBar
) is actually part of our main layout scaffolding, which looks like this:
Where App
is dynamically injected based on the page/route you're on.
There are some slots that ActionBar
has that we can use to configure it. But how can we control those slots from the App
component?
Defining the Problem
First it's a good idea to be as clear as we can about what exactly we are trying to solve.
Let's take a component that has one child component and a slot:
We can populate the slot of Parent
like this:
Nothing too fancy here...
Populating the slot of a child component is easy, that's how slots are usually used.
But is there a way that we can control what goes into the slot
of the Parent
component from inside of our Child
component?
Stated more generally:
Can we get a child component to populate the slots of a parent component?
Let's take a look at the first solution I came up with.
Props down, events up
My initial reaction to this problem was with a mantra that I keep coming back to:
Props down, events up
The only way data flows down through your component tree is through using props. And the only way you communicate back up the tree is by emitting events.
This means that if we need to communicate from a child to a parent, we use events for that.
So we'll use events to pass content into the ActionBar
s slots!
In each application component we'll need to do the following:
We package up whatever we want to put in the slot into a SlotContent
component (the name is unimportant). As soon as the application component is created, we emit the slot-content
event, passing along the component we want to use.
Our scaffold component would then look like this:
It will listen for that event, and set slotContent
to whatever our App
component sent us. Then, using the built-in Component
, we can render that component dynamically.
Passing around components with events feels weird though, because it's not really something that 'happens' in our app. It's just part of the way the app was designed.
Luckily there's a way we can avoid using events altogether.
Looking for other $options
Since Vue components are just Javascript objects, we can add whatever properties we want to them.
Instead of passing the slot content using events, we can just add it as a field to our component:
We'll have to slightly change how we access this component in our scaffolding:
This is more like static configuration, which is a lot nicer and cleaner 👌
But this still isn't right.
Ideally, we wouldn't be mixing paradigms in our code, and everything would be done declaratively.
But here, instead of taking our components and composing them together, we're passing them around as Javascript objects.
It would be nice if we could just write what we wanted to appear in the slot in a normal Vue way.
Thinking in portals
This is where portals come in.
And they work exactly like you would expect them to. You're able to teleport anything from one location to another. In our case, we're 'teleporting' elements from one location in the DOM somewhere else.
We're able to control where a component is rendered in the DOM, regardless of what the component tree looks like.
For example, let's say we wanted to populate a modal. But our modal has to be rendered at the root of the page so we can have it overlay properly. First we would specify what we want in the modal:
Then in our modal component we would have another portal that would render that content out:
This is certainly an improvement, because now we're actually writing HTML instead of just passing objects around. It's far more declarative and it's easier to see what's going on in the app.
Except that in some ways it isn't easier to see what's going on.
Because portals are doing some magic under the hood to render elements in different places, it completely breaks the model of how DOM rendering works in Vue. It looks like you're rendering elements normally, but it's not working normally at all. This is likely to cause lots of confusion and frustration.
There's another huge issue with this, but we'll cover that later on.
At least with adding the component to the $options
property, it's clear that you're doing something different.
I think there's a better way still.
Lifting state
'Lifting state' is a term that's thrown around the front end development circles a bit.
All it means is that you move state from a child component to a parent, or grandparent component. You move it up the component tree.
This can have profound effects on the architecture of your application. And for our purposes, it actually opens up a completely different — and simpler — solution.
Our 'state' here is the content that we are trying to pass into the slot of the ActionBar
component.
But that state is contained within the Page
component, and we can't really move page specific logic into the layout component. Our state has to stay within that Page
component that we're dynamically rendering.
So we'll have to lift the whole Page
component in order to lift the state.
Currently our Page
component is a child of the Layout
component:
Lifting it would require us to flip that around, and make the Layout
component a child of the Page
component. Our Page
component would look something like this:
And our Layout
component would now look something like this, where we can just use a slot to insert the page content:
But this doesn't let us customize anything just yet. We'll have to add some named slots into our Layout
component so we can pass in the content that should be placed into the ActionBar
.
The most straightforward way to do this would be to have a slot that replaces the ActionBar
component completely:
This way, if you don't specify the 'actionbar' slot, we get the default ActionBar
component. But you can still override this slot with your own custom ActionBar
configuration:
To me, this is the ideal way of doing things, but it does require you to refactor how you lay out your pages. That could be a huge undertaking depending on how your app is built.
If you can't do this method, my next preferred method would probably #2, using the $options
property. It's the cleanest, and most likely to be understood by anyone reading the code.
We can make this simpler
When we first defined the problem we stated it in it's more general form as this:
Can we get a child component to populate the slots of a parent component?
But really, this problem has nothing to do with props specifically. More simply, it's about getting a child component to control what is rendered outside of it's own subtree.
In it's most general form, we would state the problem as this:
What is the best way for a component to control what is rendered outside of it's subtree?
Examining each of our proposed solutions through this lens gives us an interesting new perspective.
Emitting events up to a parent
Because our component can't directly influence what happens outside of it's subtree, we instead find a component whose subtree contains the target element we are trying to control.
Then we ask it nicely to change it for us.
Static configuration
Instead of actively asking another component to do something on our behalf, we simply make the necessary information available to other components.
Portals
You may be noticing a pattern here among these first 3 methods.
So let me make this assertion:
There is no way for a component to control something outside of it's subtree.
(proving it is left as an exercise to the reader)
So each method here is a different way to get another component to do our bidding, and control the element that we are actually interested in.
The reason that portals are nicer in this regard is that they allow us to encapsulate all of this communication logic into separate components.
Lifting State
This is where things really start to change, and why lifting state is a simpler and more powerful technique than the first 3 we looked at.
Our main limitation here is that what we want to control is outside of our subtree.
Slot Example Vue Vs
The simplest solution to that:
Move the target element into our subtree so we can control it!
Lifting state — along with the logic to manipulate that state — allows us to have a larger subtree and to have our target element contained within that subtree.
If you can do this, it's the simplest way to solve this specific problem, as well as a whole class of related problems.
Keep in mind, this doesn't necessarily mean lifting the entire component. You can also refactor your application to move a piece of logic into a component higher up in the tree.
It's really just dependency injection
Some of you who are more familiar with software engineering design patterns may have noticed that what we're doing here is dependency injection — a technique we've been using for decades in software engineering.
One of it's uses is in making code that is easy to configure. In our case, we're configuring the Layout
component differently in each Page
that uses it.
When we flipped the Page
and Layout
components around, we were doing what is called an inversion of control.
In component-based frameworks the parent component controls what the child does (because it is within it's subtree), so instead of having the Layout
component controlling the Page
, we chose to have the Page
control the Layout
component.
In order to do this, we supply the Layout
component what it needs to get the job done using slots.
As we've seen, using dependency injection has the effect of making our code a lot more modular and easier to configure.
Conclusion
We went through 4 different ways of solving this problem, showing the pros and cons of each solution. Then we went a little further and transformed the problem into a more general one of controlling something outside of a component's subtree.
Slot Example Vue Test
I hope that you'll see that lifting state and dependency injection are two very useful patterns to use. They are wonderful tools for you to have in your arsenal, as they can be applied to a myriad of software development problems.
But above all, I hope you take this away:
By using some common software patterns we were able to turn a problem that only had ugly solutions into a problem that had a very elegant one.
Many other problems can be attacked in this way — by taking an ugly, complicated problem and transforming it into a simpler, easier to solve problem.
Slot Example Vuelo
If you want some more advanced content on slots, I replicated the v-for directive, showing how to use nested slots and nested scoped slots recursively. It's one of my favourite articles, so do check it out!