Damir Miladinov
Damir Miladinov

Reputation: 1374

Vue 3 Composition API - How to get the component element ($el) on which the component is mounted?

I want to use onMounted to initiate a third-party library. To do that I need the component element as its context. In Vue 2 I would get it with this.$el but not sure how to do it with composition functions. setup has two arguments and none of them contains the element.

setup(props, context) {
    onMounted(() => {
        interact($el)
            .resizable();
    })
}

Upvotes: 68

Views: 129974

Answers (5)

SethWhite
SethWhite

Reputation: 1977

As others have said, the $el attribute exists, but isn't documented. However, the new defineModel macro makes it possible to expose reactive refs simply and with more confidence:

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

const rootEl = defineModel<HTMLDivElement>("rootEl", { default: undefined });
</script>

<template>
    <div class="border shadow rounded-lg" ref="rootEl">
        <slot></slot>
    </div>
</template>

<style scoped></style>

In this example, we create a 'rootEl' model and attach it as a ref to the root element in the component. We can now use this model as follows:

<Card class="flex flex-col" v-model:rootEl="cardEl" />

It's necessary to use 'v-model', or the :prop, @update:prop, pattern to do this, as element refs are undefined until mount. When vue assigns the element to the model, the ref will be updated in a reactive way. This is useful for vueUse-like composables.

By following this pattern, you can expose as many elements as you like to parent components: v-model:button, v-model:subtitle, etc.

Upvotes: 0

JackChouMine
JackChouMine

Reputation: 1192

Some answers give a good method, I will add two methods.

one: instance.proxy.$el

two: onVnodeMounted hook

<script setup>
const instance = getCurrentInstance()
onMounted(() => {
  console.log(instance.proxy.$el)
})
function onVNodeMounted(VNode) {
  console.log('onVNodeMounted', VNode.el)
}
</script>

<template>
  <div :onVnodeMounted="onVNodeMounted">
  </div>
</template>

NOTE @VnodeMounted="onVNodeMounted" does not work when I answer. The version of Vue is 3.3.4 .

Plus onVnodeMountedonVnodeUpdated is not put in document of vue, they can be change someday. You can use they in template and jsx. getCurrentInstance is better.

When use getCurrentInstance, pay attention to template construction.

<script setup>
const instance = getCurrentInstance()
onMounted(() => {
  console.log(instance.proxy.$el)
  console.log(instance.proxy.$el.nextElementSibling)
})
</script>

<template>
  <!-- comment -->
  <div>
    get #text node by proxy.$el . nextElementSibling is root node
  </div>
</template>

proxy.$el is text node as well.

<script setup>
const instance = getCurrentInstance()
onMounted(() => {
  console.log(instance.proxy.$el)
  console.log(instance.proxy.$el.nextElementSibling)
})
</script>

<template>
  <div>tow root</div>
  <div>
    get #text node by proxy.$el . nextElementSibling is first div
  </div>
</template>

Upvotes: 1

tao
tao

Reputation: 90068

tl;dr:

In Vue 3, components are no longer limited to only 1 root element. Implicitly, this means you no longer have an $el.

You have to use ref to interact with any element in your template:

<div ref="root" />

In the setup function, you should instantiate root as const root = ref(null).

In the Options API, you can just use this.$refs.root in any method or hook, as long as it's after mounted().

As pointed out by @AndrewSee in the comments, when using a render function (not a template), you can specify the desired ref in createElement options:

render: function (createElement) {
  return createElement('div', { ref: 'root' })
}
// or, in short form:
render: h => h('div', { ref: 'root' })

initial answer:

As outlined in docs,

[...] the concepts of reactive refs and template refs are unified in Vue 3.

And you also have an example on how to ref a "root" element. Obviously, you don't need to name it root. Name it $el, if you prefer. However, doing so doesn't mean it will be available as this.$el, but as this.$refs.$el.

<template>
  <div ref="root"></div>
</template>

<script>
  import { ref, onMounted } from 'vue'

  export default {
    setup() {
      const root = ref(null)

      onMounted(() => {
        // the DOM element will be assigned to the ref after initial render
        console.log(root.value) // this is your $el
      })

      return {
        root
      }
    }
  }
</script>

In Vue 3 you're no longer limited to only one root element in <template>, so you have to specifically reference any element you want to interact with.


Update, 2 years later.
Specific syntaxes for various component styles (they are not different solutions, they are different ways of writing the same thing):

<script setup>:

<script setup>
import { ref, onMounted } from 'vue';
const root = ref(null);
onMounted(() => console.log(root.value.outerHTML));
</script>
<template>
  <div ref="root" />
</template>

<script setup lang="ts">:

<script setup lang="ts">
import { ref, onMounted } from 'vue';
const root = ref<HTMLElement | null>(null);
onMounted(() => console.log(root.value?.outerHTML));
</script>
<template>
  <div ref="root" />
</template>

Options API:

<script>
  export default {
    mounted() {
      console.log(this.$refs.root.outerHTML);
    }
  }
</script>
<template>
  <div ref="root" />
</template>

Upvotes: 95

antoni
antoni

Reputation: 5546

Just adding my 2 cents here, and wanted to correct what some other answers are saying that Vue 3 does not have $el anymore.

It does still have it: https://vuejs.org/api/component-instance.html#el.

Its use is discouraged in case of multi-root where the $el element can't be decided to a specific root element, and for consistency with single-root components (where $el works the same way as Vue 2), the documentation suggests that you use $refs instead.

For multi-root components, the $el ref is on a the closest text node before the elements (or comment for SSR hydration).

It does not mean that $el is unreliable, au contraire, since this mechanism is used internally and for SSR hydration.

In most cases, you can use $refs and you should.

But if you can't, like in the case below, you can still rely on $el:

<template>
  # an empty text node exists here in the DOM.
  <slot name="activator" :on="activatorEventHandlers">
  <other-component />
</template>

In this case (case of tooltips and context menus where an event listener is attached to a user slot), the first element is a slot, and you can't add a ref on a slot.

So using $el will point to the first text node, so this.$el.nextElementSibling will be the element given through the user slot.

Upvotes: 9

Roland
Roland

Reputation: 27729

In Vue 3 + Composition API there is no $el alternative provided.

Even if you use Vue 3 with Options API, due to the availability of Fragments, it is recommended to use template refs for direct access to DOM nodes instead of relying on this.$el.


How to initiate a third-part-library

Let's say we have a div element for the third-part-library:

<template>
  Below we have a third-party-lib
  <div class="third-party-lib"></div>
</template>

And then we want to initiate it from the Javascript:

Solution 1 (recommended): Using template refs

<script setup>
import { ref, onMounted } from 'vue';

const $thirdPartyLib = ref(null); // template ref

onMounted(() => {
  $thirdPartyLib.value.innerText = 'Dynamically loaded';
});
</script>

<template>
  Below we have a third-party-lib
  <div ref="$thirdPartyLib" class="third-party-lib"></div>
</template>

Solution 2 (not recommended): Using non-documented @VnodeMounted

<script setup>
function initLib({ el }) {
  el.innerText = 'Dynamic content';
}
</script>

<template>
  Below we have a third-party-lib
  <div class="third-party-lib" @VnodeMounted="initLib"></div>
</template>

See both solutions live

Upvotes: 7

Related Questions