Reputation: 1374
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
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
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
onVnodeMounted
、onVnodeUpdated
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
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 ref
erence 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
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
Reputation: 27729
$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
.
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>
Upvotes: 7