Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Determining if slot content is null or empty

Tags:

vue.js

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.

like image 364
John Moore Avatar asked Nov 22 '17 10:11

John Moore


2 Answers

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]
like image 69
kano Avatar answered Sep 20 '22 14:09

kano


@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];
    },
  },
};

UPDATE: Vue 3 (Composition API)

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.

like image 30
fredrivett Avatar answered Sep 19 '22 14:09

fredrivett