Slot Example Vue

Slot Example Vue Rating: 3,6/5 6275 votes

Note: Vue 2.6 deprecated the slot attribute in favor of v-slot, and requires it to be added to a template tag (while slot could be applied to any tag) Scoped slots. In a slot, we can’t access the data contained in the child component from the parent. Vue recognizes this use case and provides us a way to do so. Because Vue supports Dynamic Slot Names, you can use variables to set the slot names using the v-bind:attributeName='value' syntax. This way you could do something like: slot:'cell(' + b + ')'='data' v-for='b in booleanFields' But using the quotes there is not possible due to the dynamic argument expression constraints. Would you like to create a new example? Head over to the examples repo on GitHub.GitHub.

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.

Slot Example Vuelos

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 ActionBars 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.

Vue

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.

Slot

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

Slot Example Vue Test

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.

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.

Slot

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.

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.

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!

Next Up, Let’s review Named Slots in VueJS with the help of an example. We covered a lesson on slots in VueJS in the last section. slots help us to take the defined content inside the HTML markup of the component and put them in wherever specified in the template. The problem is it only works for a single default slot.

But what if we want to create more complex markup, where we are looking to accept different content from the user which we have to place at different locations.

Let’s understand this with the help of an example.

We are looking to convert the Bootstrap Card markup to a Vue Component. Here is generic HTML markup of a Bootstrap card.

And this gives out a generic card styling in the output.

We want the user to be able to specify content to be slotted in the card header, card body, and card footer.

Named Slots in VueJS

Let’s approach it using named slots in VueJS.

Here is the Vue Component code, we have defined the Bootstrap Card markup in our HTML and we have replaced the text with slot element.

Notice that the slots are named. The name of the first slot is header, The second one is without a slot that means this is the default slot, the name of the third slot is the footer.

Now let’s see how we can pas content to different slots from our HTML markup.

We have defined the first template with v-slot:header directive inside the component markup. This denotes that text that should go into the header slot. There is a similar template with v-slot:footer directive which denotes the content that should go into footer slot.

Also, there is HTML content defined without any directive which gives an indication to VueJS to put it inside the default slot.

Fallback Content

VueJS slots also have provision for fallback content is no content is supplied to the slot.

Notice our VueJS component for footer slot we have defined a fallback content.

If no content is supplied to the footer slot from the markup, then the slot will be replaced with the default content provided.

That’s all about Named Slots in VueJS.

Related Articles