Vue.js Watchers
Sometimes you may want to watch data for changes and react to them. Typically all you need is computed properties, but there are some scenarios where you need to implement a custom watcher. Before we talk more about when to use watchers, let’s first see an example of how to use them. We will build a live search which returns search results as we type into a text input field. We will simulate fetching search results from an API by using JavaScript’s setTimeout function.
I have already prepared the template as well as some data properties. All it does so far, is to bind the value of the text input field to the searchQuery data property. The search results will be stored within the results property, which will be an array of strings. To tell whether or not we are currently fetching search results, I will use an isSearching boolean property. This is all something that we have seen before.
I want to trigger a search whenever the value of the searchQuery property changes, so how can I do that? I can use something called a watcher. Watchers are functions that are invoked whenever the values of the data properties that they listen to are changed. Therefore we associate a watcher with a data property. Watchers are added within a property named watch, which is added at the top level of the Vue instance, similar to data, methods and computed, for instance.
This property should be an object with the same syntax as the methods object, but with one key difference. While the values of the nested properties are indeed functions, the names of the keys must correspond to the names of data properties. So if we want to watch the searchQuery data property for changes, we need to enter searchQuery as the key of our watcher.
watch: {
searchQuery: function(query) {
}
}
This is how we tell Vue.js which data property the watcher is watching and thereby reacting to. Notice that I added a parameter to the watcher function. This is the new value for the data property, which is passed as an argument by Vue.js whenever the value of the searchQuery property is changed. Just to show you that this function is indeed called whenever the data property is changed, I will log the new value to the console and try to type something in the text input.
searchQuery: function(query) {
console.log(query);
}
As I type, we can see the search query being output in the console.
Alright, so with that part working, let’s get to implementing the actual search logic. The first thing I will do, is to set the isSearching property to true to indicate that we are now waiting for search results to come back from the server. As with methods, I will use the this keyword to access the Vue instance from within the watcher function.
searchQuery: function(query) {
this.isSearching = true;
}
Next, I will add a timeout and run a function after 500 milliseconds to simulate retrieving the results from a server.
setTimeout(function() {
}, 500);
Within this function, we will first update the results property with the search results. But wait a minute… We cannot use this within this function as we are used to, because we are now in a different scope. Let’s verify this by logging this to the console within our watcher function and also within the function that runs after a short timeout.
searchQuery: function(query) {
this.isSearching = true;
console.log(this);
setTimeout(function() {
console.log(this);
}, 500);
}
I will open my browser’s console and type a character. Notice how the first entry refers to the Vue instance, while the second one refers to the window object. So clearly we cannot access the Vue instance this way within the nested function.
So how do we access our Vue instance? Actually we have two choices. The first is to use an ES6 arrow function, because these functions bind the this of the scope in which the function is declared. To do this, all I need to do, is to remove the function keyword and add an arrow, represented by an equals and a greater than symbol.
setTimeout(() => {
...
}, 500);
Let’s try again and verify that this now refers to what we would expect within the nested function. And indeed it does! I will just revert this to what we had before.
setTimeout(function() {
...
}, 500);
The other way is to use an old trick that assigns this to a variable, which can then be referenced within the nested function. I will add a vm variable, which is short for “Vue model” and is kind of a convention for naming Vue instances, and assign this to it.
var vm = this;
With this variable at hand, I can now access the Vue instance from within the nested function. Let’s just log it to the console and ensure that it references what we expect.
setTimeout(function() {
console.log(vm);
}, 500);
As we can see, vm now refers to the Vue instance.
So with that out of the way, we can now get back to actually implementing the search functionality. I will first set the search results to an array of strings containing “JavaScript,” “PHP,” and “MySQL.”
vm.results = ['JavaScript', 'PHP', 'MySQL'];
The last thing we need to do, is to set the isSearching property to false.
vm.isSearching = false;
Let’s see the code in action. Once I begin typing, we can see that a text is displayed stating that search results are being retrieved. After a short moment, we see a list of search results, which is what we wanted.
So what’s the big deal, you might ask. Why couldn’t we just use a computed property or an event listener? You could actually do the latter, but you cannot accomplish this with a computed property. The reason is that computed properties are synchronous and must return a value. An AJAX request – or a timeout function in this example – runs asynchronously, and that’s why we cannot use a computed property. Because Vue.js listens for the return value of the function. As I mentioned, you could have accomplished the same with an event listener on the keyup event, for instance, but this has the disadvantage that you manually have to handle this event and call a method, instead of just listening to data changes. And, if the data was to be changed somehow else, then our application would not react accordingly.
You will typically use a watcher when you want to asynchronously react to data changes, often for expensive or long-running operations, such as AJAX requests. You also don’t have to return anything within a watcher as you do with a computed property. This being said, you should typically use computed properties whenever possible, unless you need to do something that cannot be done with a computed property.
Now just an interesting fact to end this lecture off with. You can actually watch computed properties too, and not just normal data properties.
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!