55

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();
    })
}
Dave Mackey
  • 4,306
  • 21
  • 78
  • 136
Damir Miladinov
  • 1,224
  • 1
  • 12
  • 15

3 Answers3

80

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>
Dave Mackey
  • 4,306
  • 21
  • 78
  • 136
tao
  • 82,996
  • 16
  • 114
  • 150
  • 1
    Why is it not :ref="root" as when you use other variables? ref="root" looks like a simple string. – curly_brackets Mar 29 '22 at 13:55
  • 3
    @curly, `ref` is already binding, just like `v-on` and `v-bind` are. Ultimately, the only person who can answer your question is Evan You, the creator of Vue. – tao Mar 30 '22 at 23:37
  • While this solution may work, roland's answer using – jtalarico Apr 12 '22 at 00:50
  • 1
    @jtalarico, first of all, ` – tao Apr 12 '22 at 03:14
  • I don't disagree - just commenting for anyone new hitting this article and might have trouble deciding which way to go. BTW, there was a bug in the template ref implementation in Vue 3 that would cause it not to work on v-for elements. In Vue 2/options api, it results in an array. In Vue3/comp api, it results in a null reference. I believe a fix is in the works as of yesterday. – jtalarico Apr 13 '22 at 16:38
5

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

Roland
  • 24,554
  • 4
  • 99
  • 97
  • Could you please clarify what is `ref('')` in `const $thirdPartyLib = ref('');` and how to run your live solutions? – Niksr Feb 05 '22 at 11:48
  • I'm also getting "Property 'innerText' does not exist on type 'string'" designtime error when I'm trying to repeat your Solution 1. It looks like `const $thirdPartyLib = ref('');` is a string init. Why should string has this property? – Niksr Feb 05 '22 at 12:04
  • 1
    @Niksr the `ref('')` which now is `ref(null)` is a template ref. In order to run the live solutions, you can simply type `vite` in the terminal and then click Enter. – Roland Feb 06 '22 at 07:54
  • Got it. Many thanks! This solution, however, isn't that simple with the TS because of "Object is possibly null" error. – Niksr Feb 06 '22 at 14:14
  • I found that just `ref()` works best in all cases. – Niksr Feb 06 '22 at 15:42
4

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.

antoni
  • 5,001
  • 1
  • 35
  • 44