Vuejs Dynamic Slot Content
Smashing Newsletter
Vue.js is an easy to use web app framework that we can use to develop interactive front end apps. In this article, we’ll look at distributing components with slots and dynamic components. Distributing Components with Slots. Vue.js - The Progressive JavaScript Framework. Modal Component Example. Features used: component, prop passing, content insertion, transitions. Slot Content Vue implements a content distribution API that’s modeled after the current Web Components spec draft, using the element to serve as distribution outlets for content. This allows you to compose components like this. A dynamic table with sorting, filtering, editing, pagination, multiple select, etc. Vue-table-dynamic is a vue component of dynamic table.
This is an easy way to see how your VueJS components are created. You can also add mounted and detroyed hooks. For our experiment created is enough. Problem 1: Rerendering on route change When you search online for dynamic layouts, you will find a lot of solutions, and one of the most common is the following one.
Every week, we send out useful front-end & UX techniques. Subscribe and get the Smart Interface Design Checklists PDF delivered to your inbox.
With the recent release of Vue 2.6, the syntax for using slots has been made more succinct. This change to slots has gotten me re-interested in discovering the potential power of slots to provide reusability, new features, and clearer readability to our Vue-based projects. What are slots truly capable of?
If you’re new to Vue or haven’t seen the changes from version 2.6, read on. Probably the best resource for learning about slots is Vue’s own documentation, but I’ll try to give a rundown here.
What Are Slots?
Slots are a mechanism for Vue components that allows you to compose your components in a way other than the strict parent-child relationship. Slots give you an outlet to place content in new places or make components more generic. The best way to understand them is to see them in action. Let’s start with a simple example:
This component has a wrapper div
. Let’s pretend that div
is there to create a stylistic frame around its content. This component is able to be used generically to wrap a frame around any content you want. Let’s see what it looks like to use it. The frame
component here refers to the component we just made above.
The content that is between the opening and closing frame
tags will get inserted into the frame
component where the slot
is, replacing the slot
tags. This is the most basic way of doing it. You can also specify default content to go into a slot simply by filling it in:
So now if we use it like this instead:
The default text of “This is the default content if nothing gets specified to go here” will show up, but if we use it as we did before, the default text will be overridden by the img
tag.
Multiple/Named Slots
You can add multiple slots to a component, but if you do, all but one of them is required to have a name. If there is one without a name, it is the default slot. Here’s how you create multiple slots:
We kept the same default slot, but this time we added a slot named header
where you can enter a title. You use it like this:
Just like before, if we want to add content to the default slot, just put it directly inside the titled-frame
component. To add content to a named slot, though, we needed to wrap the code in a template
tag with a v-slot
directive. You add a colon (:
) after v-slot
and then write the name of the slot you want the content to be passed to. Note that v-slot
is new to Vue 2.6, so if you’re using an older version, you’ll need to read the docs about the deprecated slot syntax.
Scoped Slots
One more thing you’ll need to know is that slots can pass data/functions down to their children. To demonstrate this, we’ll need a completely different example component with slots, one that’s even more contrived than the previous one: let’s sorta copy the example from the docs by creating a component that supplies the data about the current user to its slots:
This component has a property called user
with details about the user. By default, the component shows the user’s last name, but note that it is using v-bind
to bind the user data to the slot. With that, we can use this component to provide the user data to its descendant:
To get access to the data passed to the slot, we specify the name of the scope variable with the value of the v-slot
directive.
There are a few notes to take here:
- We specified the name of
default
, though we don’t need to for the default slot. Instead we could just usev-slot='slotProps'
. - You don’t need to use
slotProps
as the name. You can call it whatever you want. - If you’re only using a default slot, you can skip that inner
template
tag and put thev-slot
directive directly onto thecurrent-user
tag. - You can use object destructuring to create direct references to the scoped slot data rather than using a single variable name. In other words, you can use
v-slot='{user}'
instead ofv-slot='slotProps'
and then you can useuser
directly instead ofslotProps.user
.
Taking those notes into account, the above example can be rewritten like this:
Vuejs Dynamic Slot Content Tool
A couple more things to keep in mind:
- You can bind more than one value with
v-bind
directives. So in the example, I could have done more than justuser
. - You can pass functions to scoped slots too. Many libraries use this to provide reusable functional components as you’ll see later.
v-slot
has an alias of#
. So instead of writingv-slot:header='data'
, you can write#header='data'
. You can also just specify#header
instead ofv-slot:header
when you’re not using scoped slots. As for default slots, you’ll need to specify the name ofdefault
when you use the alias. In other words, you’ll need to write#default='data'
instead of#='data'
.
There are a few more minor points you can learn about from the docs, but that should be enough to help you understand what we’re talking about in the rest of this article.
What Can You Do With Slots?
Slots weren’t built for a single purpose, or at least if they were, they’ve evolved way beyond that original intention to be a powerhouse tool for doing many different things.
Reusable Patterns
Components were always designed to be able to be reused, but some patterns aren’t practical to enforce with a single “normal” component because the number of props
you’ll need in order to customize it can be excessive or you’d need to pass large sections of content and potentially other components through the props
. Slots can be used to encompass the “outside” part of the pattern and allow other HTML and/or components to placed inside of them to customize the “inside” part, allowing the component with slots to define the pattern and the components injected into the slots to be unique.
For our first example, let’s start with something simple: a button. Imagine you and your team are using Bootstrap*. With Bootstrap, your buttons are often strapped with the base `btn` class and a class specifying the color, such as `btn-primary`. You can also add a size class, such as `btn-lg`.
* I neither encourage nor discourage you from doing this, I just needed something for my example and it’s pretty well known.
Let’s now assume, for simplicity’s sake that your app/site always uses btn-primary
and btn-lg
. You don’t want to always have to write all three classes on your buttons, or maybe you don’t trust a rookie to remember to do all three. In that case, you can create a component that automatically has all three of those classes, but how do you allow customization of the content? A prop
isn’t practical because a button
tag is allowed to have all kinds of HTML in it, so we should use a slot.
Now we can use it everywhere with whatever content you want:
Of course, you can go with something much bigger than a button. Sticking with Bootstrap, let’s look at a modal, or least the HTML part; I won’t be going into functionality… yet.
Now, let’s use this:
The above type of use case for slots is obviously very useful, but it can do even more.
Reusing Functionality
Vue components aren’t all about the HTML and CSS. They’re built with JavaScript, so they’re also about functionality. Slots can be useful for creating functionality once and using it in multiple places. Let’s go back to our modal example and add a function that closes the modal:
Now when you use this component, you can add a button to the footer that can close the modal. Normally, in the case of a Bootstrap modal, you could just add data-dismiss='modal'
to a button, but we want to hide Bootstrap specific things away from the components that will be slotting into this modal component. So we pass them a function they can call and they are none the wiser about Bootstrap’s involvement:
Renderless Components
And finally, you can take what you know about using slots to pass around reusable functionality and strip practically all of the HTML and just use the slots. That’s essentially what a renderless component is: a component that provides only functionality without any HTML.
Making components truly renderless can be a little tricky because you’ll need to write render
functions rather than using a template in order to remove the need for a root element, but it may not always be necessary. Let’s take a look at a simple example that does let us use a template first, though:
This is an odd example of a renderless component because it doesn’t even have any JavaScript in it. That’s mostly because we’re just creating a pre-configured reusable version of a built-in renderless function: transition
.
Yup, Vue has built-in renderless components. This particular example is taken from an article on reusable transitions by Cristi Jora and shows a simple way to create a renderless component that can standardize the transitions used throughout your application. Cristi’s article goes into a lot more depth and shows some more advanced variations of reusable transitions, so I recommend checking it out.
For our other example, we’ll create a component that handles switching what is shown during the different states of a Promise: pending, successfully resolved, and failed. It’s a common pattern and while it doesn’t require a lot of code, it can muddy up a lot of your components if the logic isn’t pulled out for reusability.
So what is going on here? First, note that we are receiving a prop called promise
that is a Promise
. In the watch
section we watch for changes to the promise and when it changes (or immediately on component creation thanks to the immediate
property) we clear the state, and call then
and catch
on the promise, updating the state when it either finishes successfully or fails.
Then, in the template, we show a different slot based on the state. Note that we failed to keep it truly renderless because we needed a root element in order to use a template. We’re passing data
and error
to the relevant slot scopes as well.
And here’s an example of it being used:
We pass in somePromise
to the renderless component. While we’re waiting for it to finish, we’re displaying “Working on it…” thanks to the pending
slot. If it succeeds we display “Resolved:” and the resolution value. If it fails we display “Rejected:” and the error that caused the rejection. Now we no longer need to track the state of the promise within this component because that part is pulled out into its own reusable component.
So, what can we do about that span
wrapping around the slots in promised.vue
? To remove it, we’ll need to remove the template
portion and add a render
function to our component:
There isn’t anything too tricky going on here. We’re just using some if
blocks to find the state and then returning the correct scoped slot (via this.$scopedSlots['SLOTNAME'](...)
) and passing the relevant data to the slot scope. When you’re not using a template, you can skip using the .vue
file extension by pulling the JavaScript out of the script
tag and just plunking it into a .js
file. This should give you a very slight performance bump when compiling those Vue files.
This example is a stripped-down and slightly tweaked version of vue-promised, which I would recommend over using the above example because they cover over some potential pitfalls. There are plenty of other great examples of renderless components out there too. Baleada is an entire library full of renderless components that provide useful functionality like this. There’s also vue-virtual-scroller for controlling the rendering of list item based on what is visible on the screen or PortalVue for “teleporting” content to completely different parts of the DOM.
I’m Out
Vue’s slots take component-based development to a whole new level, and while I’ve demonstrated a lot of great ways slots can be used, there are countless more out there. What great idea can you think of? What ways do you think slots could get an upgrade? If you have any, make sure to bring your ideas to the Vue team. God bless and happy coding.
# component
Props:
is
-string Component
Usage:
A 'meta component' for rendering dynamic components. The actual component to render is determined by the
is
prop. Anis
prop as a string could be either an HTML tag name or a Component name.Example:
See also:Dynamic Components
# transition
Props:
name
-string
Used to automatically generate transition CSS class names. e.g.name: 'fade'
will auto expand to.fade-enter
,.fade-enter-active
, etc.appear
-boolean
, Whether to apply transition on initial render. Defaults tofalse
.persisted
-boolean
. If true, indicates this is a transition that doesn't actually insert/remove the element, but toggles the show / hidden status instead. The transition hooks are injected, but will be skipped by the renderer. Instead, a custom directive can control the transition by calling the injected hooks (e.g.v-show
).css
-boolean
. Whether to apply CSS transition classes. Defaults totrue
. If set tofalse
, will only trigger JavaScript hooks registered via component events.type
-string
. Specifies the type of transition events to wait for to determine transition end timing. Available values are'transition'
and'animation'
. By default, it will automatically detect the type that has a longer duration.mode
-string
Controls the timing sequence of leaving/entering transitions. Available modes are'out-in'
and'in-out'
; defaults to simultaneous.duration
-number {
enter: number,
leave: number }
. Specifies the duration of transition. By default, Vue waits for the firsttransitionend
oranimationend
event on the root transition element.enter-from-class
-string
leave-from-class
-string
appear-class
-string
enter-to-class
-string
leave-to-class
-string
appear-to-class
-string
enter-active-class
-string
leave-active-class
-string
appear-active-class
-string
Events:
before-enter
before-leave
enter
leave
appear
after-enter
after-leave
after-appear
enter-cancelled
leave-cancelled
(v-show
only)appear-cancelled
Usage:
<transition>
serve as transition effects for single element/component. The<transition>
only applies the transition behavior to the wrapped content inside; it doesn't render an extra DOM element, or show up in the inspected component hierarchy.See also:Enter & Leave Transitions
# transition-group
Props:
tag
-string
, if not defined, renders without a root element.move-class
- overwrite CSS class applied during moving transition.- exposes the same props as
<transition>
exceptmode
.
Events:
- exposes the same events as
<transition>
.
- exposes the same events as
Usage:
<transition-group>
provides transition effects for multiple elements/components. By default it doesn't render a wrapper DOM element, but one can be defined via thetag
attribute.Note that every child in a
<transition-group>
must be uniquely keyed for the animations to work properly.<transition-group>
supports moving transitions via CSS transform. When a child's position on screen has changed after an update, it will get applied a moving CSS class (auto generated from thename
attribute or configured with themove-class
attribute). If the CSStransform
property is 'transition-able' when the moving class is applied, the element will be smoothly animated to its destination using the FLIP technique(opens new window).See also:List Transitions
# keep-alive
Props:
include
-string RegExp Array
. Only components with matching names will be cached.exclude
-string RegExp Array
. Any component with a matching name will not be cached.max
-number string
. The maximum number of component instances to cache.
Usage:
When wrapped around a dynamic component,
<keep-alive>
caches the inactive component instances without destroying them. Similar to<transition>
,<keep-alive>
is an abstract component: it doesn't render a DOM element itself, and doesn't show up in the component parent chain.When a component is toggled inside
<keep-alive>
, itsactivated
anddeactivated
lifecycle hooks will be invoked accordingly.Primarily used to preserve component state or avoid re-rendering.
Note,
<keep-alive>
is designed for the case where it has one direct child component that is being toggled. It does not work if you havev-for
inside it. When there are multiple conditional children, as above,<keep-alive>
requires that only one child is rendered at a time.include
andexclude
The
include
andexclude
props allow components to be conditionally cached. Both props can be a comma-delimited string, a RegExp or an Array:The match is first checked on the component's own
name
option, then its local registration name (the key in the parent'scomponents
option) if thename
option is not available. Anonymous components cannot be matched against.max
The maximum number of component instances to cache. Once this number is reached, the cached component instance that was least recently accessed will be destroyed before creating a new instance.
WARNING
<keep-alive>
does not work with functional components because they do not have instances to be cached.See also:Dynamic Components - keep-alive
# slot
Vuejs Dynamic Slot Content Tutorial
Props:
name
-string
, Used for named slot.
Usage:
<slot>
serve as content distribution outlets in component templates.<slot>
itself will be replaced.For detailed usage, see the guide section linked below.
See also:Content Distribution with Slots
# teleport
Props:
to
-string
. Required prop, has to be a valid query selector, or an HTMLElement (if used in a browser environment). Specifies a target element where<teleport>
content will be moved
disabled
-boolean
. This optional prop can be used to disable the<teleport>
's functionality, which means that its slot content will not be moved anywhere and instead be rendered where you specified the<teleport>
in the surrounding parent component.
Notice that this will move the actual DOM nodes instead of being destroyed and recreated, and it will keep any component instances alive as well. All stateful HTML elements (i.e. a playing video) will keep their state.
See also:Teleport component