I have a little Loading component, whose default text I want to be 'Loading...'. Good candidate for slots, so I have something like this as my template:
<p class="loading"><i class="fa fa-spinner fa-spin"></i><slot>Loading...</slot></p>
That allows me to change the loading message with e.g. <loading>Searching...</loading>
. The behaviour I would like, though, is not just to display the default message if no slot content is supplied, but also if the slot content is null or blank. At the moment if I do e.g.<loading>{{loadingMessage}}</loading>
and loadingMessage is null, no text is displayed (where I want the default text to be displayed). So ideally I need to test this.$slots.default
. This tells me whether content was passed in, but how do I find whether or not it was empty? this.$slots.default.text
returns undefined.
You'd need a computed property which checks for this.$slots
. With a default slot you'd check this.$slots.default
, and with a named slot just replace default
with the slot name.
computed: {
slotPassed() {
return !!this.$slots.default[0].text.length
}
}
And then use it in your template:
<template>
<div>
<slot v-if="slotPassed">Loading...</slot>
<p v-else>Searching...</p>
</div>
</template>
You can see a small example here. Notice how fallback content is displayed and not "default content", which is inside the slot.
Edit:
My wording could've been better. What you need to do is check for $slots.X
value, but computed property is a way to check that. You could also just write the slot check in your template:
<template>
<div>
<slot v-if="!!$slots.default[0].text">Loading...</slot>
<p v-else>Searching...</p>
</div>
</template>
Edit 2: As pointed out by @GoogleMac in the comments, checking for a slot's text property fails for renderless components (e.g. <transition>
, <keep-alive>
, ...), so the check they suggested is:
!!this.$slots.default && !!this.$slots.default[0]
// or..
!!(this.$slots.default || [])[0]
@kano's answer works well, but there's a gotcha: this.$slots
isn't reactive, so if it starts out being false
, and then becomes true
, any computed
property won't update.
The solution is to not rely on a computed
value but instead on created
and beforeUpdated
(as @MathewSonke points out):
export default {
name: "YourComponentWithDynamicSlot",
data() {
return {
showFooter: false,
showHeader: false,
};
},
created() {
this.setShowSlots();
},
beforeUpdate() {
this.setShowSlots();
},
methods: {
setShowSlots() {
this.showFooter = this.$slots.footer?.[0];
this.showHeader = this.$slots.header?.[0];
},
},
};
For Vue 3, it seems that the way to check whether a slot has content has changed (using the new composition API):
import { computed, defineComponent } from "vue";
export default defineComponent({
setup(_, { slots }) {
const showHeader = computed(() => !!slots.header);
return {
showHeader,
};
},
});
note: I can't find any documentation on this, so take it with a pinch of salt, but seems to work in my very limited testing.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With