Accessing the DOM in Vue.js with $refs
It’s time to take a closer look at a few more special Vue instance properties and methods, with the first one being the $refs property. But before diving into the JavaScript part of things, let’s start by taking a look at the template.
<div id="app">
<h1>{{ message }}</h1>
<button @click="clickedButton">Click Me!</button>
</div>
var vm = new Vue({
el: '#app',
data: {
message: 'Hello World!'
},
methods: {
clickedButton: function() {
}
}
});
By adding a ref attribute to any element within the template, we can refer to these elements on our Vue instance. More specifically, we can access the DOM elements. So let’s try it out on the button that I have added in advance. The button already has a click event handler which doesn’t do anything yet.
<button ref="myButton" @click="clickedButton">Click Me!</button>
Note that the ref attribute is not a standard HTML attribute, so it is only used by Vue. In fact, it won’t even be part of the DOM, so if you inspect the rendered HTML, you won’t see a sign of it. And, since we didn’t prefix it with a colon, it is not a directive either.
We can now reference this button by using the name myButton. We can do this by using the $refs property on our Vue instance. Let’s log this to the console and see what it looks like.
var vm = new Vue({
el: '#app',
data: {
message: 'Hello World!'
},
methods: {
clickedButton: function() {
console.log(this.$refs);
}
}
});
So if I open up the console, we can see that this property is a JavaScript object holding references to all of the elements on which we have added a ref attribute.
Notice that the key names within this object match the names that we specify within the ref attribute, and the values are the DOM elements. In this case, we can see that the key is myButton and that the value is a native button element which has nothing to do with Vue.js.
Therefore we can simply access the DOM element by accessing the name of our reference as a property on the $refs object. Let’s see that in action. As an example, I will change the text of the button when clicking it.
var vm = new Vue({
el: '#app',
data: {
message: 'Hello World!'
},
methods: {
clickedButton: function() {
console.log(this.$refs);
this.$refs.myButton.innerText = this.message;
}
}
});
Clicking the button now, will change its text to “Hello World!”
Of course we could also have accomplished this with vanilla JavaScript by using a query selector to get access to the DOM element, but using the ref attribute is much cleaner and is the Vue way of doing it. It’s also safer because you won’t be relying on classes and IDs, so the chances of you breaking your code as a result of changing the markup or CSS styles, are lower.
One of the main purposes of a JavaScript framework like Vue is for developers to not have to deal with the DOM. Therefore you should avoid doing what I just showed you unless you really need to. There is a potential problem that you should be aware of, which I will show you now.
Let’s first add a ref attribute to our h1 element.
<h1 ref="message">{{ message }}</h1>
Since I have already assigned the Vue instance to a variable, I can just go ahead and use it. What I want to do, is to change the text of the element. Since it will initially contain the value of the message data property, I will just apply a timeout so that we can see what happens.
setTimeout(function() {
vm.$refs.message.innerText = 'This is a test';
}, 2000);
Let’s run it and see that the text changes after two seconds.
Next, I will change the message data property two seconds later.
setTimeout(function() {
vm.message = 'This is another test';
}, 4000);
Let’s run the code again and see what happens.
As you can see, the change that we made to the DOM is overwritten when updating the data property. The reason for this, is that when accessing the DOM elements and manipulating them directly, we are essentially skipping the virtual DOM as we discussed in a previous post. So Vue is still controlling the h1 element because it holds a copy of the template, and when Vue reacts to changes in the data property, it updates the virtual DOM and thereafter the DOM itself. Therefore you should be careful with applying changes to the DOM directly like this, as any changes you apply may be overwritten if you are not careful. While you should be cautious with changing the DOM when using references, it is safer to do read-only operations such as reading values from the DOM.
As the last thing, I want to show you how you can also use the ref attribute on elements with the v-for directive. I will just add an unordered list consisting of the numbers from one to ten, which I will output using the v-for directive.
<ul>
<li v-for="n in 10" ref="numbers">{{ n }}</li>
</ul>
We have already added a line that logs the $refs property to the console when clicking the button, so let’s open up the console and see what it looks like.
A numbers property has been added to the object as we would expect, but notice the type of the value. Instead of being the DOM element as we saw before, it’s actually an array – an array of DOM elements. When using the ref attribute together with the v-for directive, Vue collects the DOM elements for all of the iterations of the loop and places them within an array. In this case, this gives us an array of ten li DOM elements, because our loop iterates ten times. Each of these elements can then be used exactly as we saw before.
And that’s about all you need to know about the $refs property.
Here is what you will learn:
- How to build advanced Vue.js applications (including SPA)
- How Vue.js works under the hood
- Communicating with services through HTTP
- Managing state of large applications with Vuex
- ... and much more!
8 comments on »Accessing the DOM in Vue.js with $refs«
Didn’t know before that we use $refs for loop element, that’s nice Bo :)
love this man! learned a lot from this blog. :) Really appreciate it! :)
Glad to hear that, Simon, and thanks for the feedback! :-)
Thanks for your good posting. I learned a lot about refs. I am not good at english, but You explained it very easily. If you do not mind, can I translate your article into korean?
Problem is that whenever you try to update a $refs property Vue warns you to avoid mutating props directly :( Antipattern way.
Thanks for posting about $refs.
Nice writeup but ended so quickly :-) Was expecting to see more of the last scenario
how can i assign css class to 1 element in v-for by ref