Reputation: 371
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
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
Reputation: 4443
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>
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
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
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
Reputation: 5556
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
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