How to get HTMLElement from child component in slot?

Viewed 895

How can I get the Dom element of the child in slot. Attempts to get in the life hook onMounted () the el vnode property of an object is null

My-Component:

<template>
   <div>{{text}}</div>
   <slot></slot>
</template>

<script lang="ts">
import { defineComponent, onMounted } from "vue";

export default defineComponent({
    name: "my-component",
    props: {
        text: {
            type: String,
            default: '',
            required: true,
        },
    },
    setup( _, ctx) {
        
        onMounted( () => {
            if (ctx.slots.default) {
                console.log( (ctx.slots.default()[0]) ); // <- property "el" - is null
            }
        })

    },
});
</script>
1 Answers

Couple things to keep in mind (for others looking to do the same)

  • ✅ The slots object is passed in through the context which is the second parameter
  • ✅ Unnamed slot content is accessed as default
  • ✅ slots.default is a function that needs to be executed
  • ✅ slots.default() returns an array, because a slot can have multiple elements, in most cases you can just pick [0]
  • ❌ The content is only available after the component is rendered. In the example I used onMounted, which is probably the easiest.
  • But, if the passed element is a Vue component, the element is simply not available

The slot is accessible through a function slots.default() that takes a data parameter that is used to render the slot. This can be useful for generating the nodes, if you want to use a render function, and seems to a large degree just some exposed internals.

I've found a couple hacks though to get around it using node traversal.

One (probably the better option) uses the child object (slots.default()[0].children.default()[0].el.parentElement)

And the other using a ref and a nextElementSibling.

The idea is that you put a span (or some other "invisible" element) that you can get a reference to using ref. Then in the onMounted handler, you can use that element to get to the slot element which is going to be the next element sibling.

var app = Vue.createApp({
  setup() {return {}}
});

app.component("parent-component", {
  template: `<div><span ref="slotRef"></span><slot></slot></div>`,
  setup(props, ctx) {
    const slotRef = Vue.ref(null); // create a ref
    Vue.onMounted(()=>{
      const defaultSlot = ctx.slots.default();
      console.log(defaultSlot[0].children.default()[0].el.parentElement) // or get the parent of the first child
      defaultSlot[0].children.default()[0].el.parentElement.innerText += ""
      console.log(slotRef.value.nextElementSibling); // get the Sibling of ref
      slotRef.value.nextElementSibling.innerText += ""
    })
    return {slotRef};
  }
});
app.component("child-component", {
  template: `<div><slot></slot></div>`,
});

app.mount("#app");
div{border:1px solid; padding:2px;}
<script src="https://unpkg.com/vue@3.0.11/dist/vue.global.prod.js"></script>
<div id="app">
  <parent-component>
    <child-component>
      
    </child-component>
  </parent-component>
</div>

Related