Tadas Majeris
Tadas Majeris

Reputation: 371

How to reference text that's in '<slot></slot>' in Vue.js

How to reference text that's in in Vue.js?

Vue.component('component', {
  template: `<button><slot></slot></button>`,
  created: function() {
    // i would like to access the text in slot here
  }
});

Upvotes: 18

Views: 27587

Answers (6)

fsdevland
fsdevland

Reputation: 21

This Vue composable using Typescript returns the reactive text or texts contained in a slot

import { useSlots, ref, type VNode, computed, watch } from 'vue';


/**
 * Get reactive reference to the current textContent of slot.
  @param {string} name the name of the slot
  @param {object} slotProps the slot bindings(props) as an object
  @returns {string} the textContent of the slot
  */
const useSlotText = <T>(name: string, slotProps: T) => {
  const slot = useSlots()[name]
  const text = ref('')

  //get text from every element in the slot recursively
  function getText(nodes?: VNode[]) {
    let text = ""
    nodes?.forEach(node => {
      if (typeof node.children === 'string') {
        text += node.children
        return
      } else if (Array.isArray(node.children) && node.children.length) {
        return getText(node.children as VNode[])
      }
    })
    return text
  }

  if (slot) {
    const nodes = slot(slotProps)
    text.value = getText(nodes)
  }

  //watch for changes and update
  watch(() => useSlots()[name], () => {
    const slot = useSlots()[name]
    if (slot)
      text.value = getText(slot(slotProps))
  })

  return computed(() => text.value)
}

Upvotes: 0

Happyriri
Happyriri

Reputation: 4443

Run the code snippet below that get the slot text passed by parent :

I'm using "ref" :

<span ref="mySlot">

this.$refs.mySlot.innerHTML

Careful : <slot ref="refName"></slot> don't works because <slot> are not render on html. You have to wrap the <slot></slot> with <div></div> or <span></span>

The code :

Vue.component('component', {
  template: '<button>' +
              '<span ref="mySlot">' +
              
                  'Text before<br />' +
                  
                  '<slot name="slot1">' +
                      'Text by default' +
                  '</slot>' +
                  
                  '<br />Text after' +
                  
              '</span>' +
          '</button>',
  mounted: function() {
    console.log( this.$refs.mySlot.innerHTML);
  }
});

new Vue({
    el: '#app'
});
<script src="https://vuejs.org/js/vue.min.js"></script>

<div id="app">
  <component>
    <span slot="slot1">I'm overriding the slot and text appear in this.$refs.mySlot.innerHTML !</span>
  </component>
</div>

Upvotes: 8

Herobrine
Herobrine

Reputation: 3233

My use case was pretty simple, I had a default slot with only text. Here's how I accessed the text in vue3 with script setup:

<script setup lang="ts">
import { computed, useSlots } from "vue";

const slots = useSlots();

const slotText = computed(() => {
    return slots.default()[0].children; // This is the interesting line
});
</script>

Upvotes: 5

Bert
Bert

Reputation: 82489

Note: This answer applies to Vue v2 only.

The content inside the default slot, which is what you are describing, is exposed as this.$slots.default in the Vue. So the most naive way to get the text inside your button would be to use this.$slots.default[0].text.

Vue.component('component', {
  template: `<button><slot></slot></button>`,
  created: function() {
    const buttonText = this.$slots.default[0].text;
  }
});

The problem is that there may be more than one node inside the slot, and the nodes may not necessarily be text. Consider this button:

<button><i class="fa fa-check"></i> OK</button>

In this case, using the first solution will result in undefined because the first node in the slot is not a text node.

To fix that we can borrow a function from the Vue documentation for render functions.

var getChildrenTextContent = function (children) {
  return children.map(function (node) {
    return node.children
      ? getChildrenTextContent(node.children)
      : node.text
  }).join('')
}

And write

Vue.component("mybutton", {
  template:"<button><slot></slot></button>",
  created(){
    const text = getChildrenTextContent(this.$slots.default); 
    console.log(text)
  }
})

Which will return all the text in the slot joined together. Assuming the above example with the icon, it would return, "OK".

Upvotes: 17

antoni
antoni

Reputation: 5556

For Vue 3.

The answer from @bert works well on Vue 2, but Vue 3 slots have a more complex structure.

Here is one way to get the slots text contents (from default slot) on Vue 3.

const getSlotChildrenText = children => children.map(node => {
  if (!node.children || typeof node.children === 'string') return node.children || ''
  else if (Array.isArray(node.children)) return getSlotChildrenText(node.children)
  else if (node.children.default) return getSlotChildrenText(node.children.default())
}).join('')


const slotTexts = this.$slots.default && getSlotChildrenText(this.$slots.default()) || ''
console.log(slotTexts)

Upvotes: 10

Vitim.us
Vitim.us

Reputation: 22188

You can access the slot text by joining the innerText of all the children inside the slot.

getSlotText() {
  return this.$slots.default.map(vnode => (vnode.text || vnode.elm.innerText)).join('');
},

Upvotes: 5

Related Questions