Mr.Smithyyy
Mr.Smithyyy

Reputation: 1329

Ref added programmatically not applied

I would like to attach a ref attribute to an HTML element that has a custom directive.

Let's say I have a directive named v-custom and whenever it is used on an element, I would like ref="elem" to be added to the element.

I have tried using directives like so:

Vue.directive('custom', {
    inserted(el) {
        el.setAttribute("ref", "elem");
    }
});

And in one of my components I have this:

<span v-custom>Testing</span>

And when I view it in a web page I can inspect that span element and see that it has the ref attribute but when I inspect the refs of the component it belongs to it says that it contains no "elem" ref.

However, If I add the ref tag myself like so:

<span v-custom ref="elem">Testing</span>

Then it works as intended and I can see the "elem" ref in the console.

Is there any way to get my use case working or is this intended behavior?

Upvotes: 3

Views: 1000

Answers (1)

Leonard Pauli
Leonard Pauli

Reputation: 2673

As @skirtle noted, ref is written as a normal DOM attribute in the vue template code, but is handled differently when parsed. A vue component instance/view model/"vm" has an object vm.$refs which maps keys to DOM elements. We can modify this object ourself. The issue then is how to get the parent vm from within the directive (we already got the DOM element el).

Looking at the documentation for custom directives https://v2.vuejs.org/v2/guide/custom-directive.html#Directive-Hook-Arguments, we can see that the third argument is a "vnode" reference, and looking at its documentation, we can see that vnode.context references the container vm; thus:

Vue.directive('my-directive', {
    inserted (el, binding, vnode) {
        console.log('directive inserted')
        const refKey = "s2"
        vnode.context.$refs[refKey] = el // set it
    }
})

Vue.component('my-component', {
    template: '#my-component-template',
    replace: false,
    props: {text: String},
    mounted () {
        console.log('component mounted')
        this.$refs.s1.innerText = 's1 ref working'
        this.$refs.s2.innerText = 's2 ref working' // use it
    }
});

new Vue({
    el: '#app',
    data: {
        status: "initialized",
    },
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
    hello
    <my-component :text="status"></my-component>
</div>

<script type="text/x-template" id="my-component-template">
    <div>
        {{text}}, <!-- to illustrate props data passing working as usual -->
        <span ref="s1"></span>, <!-- to illustrate a normal ref -->
        <span class="nested"> <!-- to illustrate vnode.context doesn't just get the parent node -->
            <span v-my-directive></span>
        </span>
    </div>
</script>

Running this example, we can see that the v-my-directive successfully modifies vm.$refs.s2 to reference the DOM-element with the directive, before the mounted function in the vm is run, where we can use the reference.

Beware that you probably would like some logic to not overwrite the ref if more that one elements contains the directive.

Happy coding!

Upvotes: 1

Related Questions