Adding and Removing Vue.js Watchers Dynamically

Published on April 23, 2017 by

In this post, I want to show you how to add watchers dynamically, i.e. “outside” of the Vue instance. If you have assigned a Vue instance to a variable as we have here, then there is a $watch instance method available on this variable. This method let’s you add a watcher, so let’s go ahead and do that.

So in the example that I have prepared, we simply have a counter variable and a button which increments this counter.

var vm = new Vue({
	el: '#app',
	data: {
		counter: 1
	}
});
<div id="app">
	<p>Counter: {{ counter }}</p>
  
	<button @click="counter++">Increase Counter</button>
</div>

Now I want to add a watcher to be notified whenever the value changes. Of course I could do this by implementing a watcher as we saw earlier in the series, but instead I want to show you how to do it dynamically.

vm.$watch('counter', function(newValue, oldValue) {
	alert('Counter changed from ' + oldValue + ' to ' + newValue + '!');
});

Note that you cannot use ES6 arrow functions here, because arrow functions are bound to the parent context, and the this keyword would then not be bound correctly to the Vue instance.

When clicking the button now, we see an alert displaying the old and new value of the counter data property.

We can also specify a key path as the expression, meaning that we can use a dot notation to refer to properties within nested objects. To show you this, I will first add a person object within the data object.

var vm = new Vue({
	el: '#app',
	data: {
		counter: 1,
		person: {
			name: {
				firstName: 'Bo',
				lastName: 'Andersen'
			}
		}
	}
});

If we wanted to watch the firstName property for changes, we can separate each property name by a dot, as follows.

vm.$watch('person.name.firstName', function(newValue, oldValue) {
	alert('First name changed from ' + oldValue + ' to ' + newValue + '!');
});

To change the first name, I’ll just add a button for this.

<button @click="person.name.firstName = 'Andy'">Change First Name</button>

Clicking the button shows the alert as we would expect, so this is how you can use a key path as the expression.

But what if we wanted to watch an object for changes instead of a specific property within the object? Perhaps we just wanted to be notified whenever something changes within the name object instead of listening to the firstName property specifically.

vm.$watch('person.name', function(newValue, oldValue) {
	alert('The first name was changed from ' + oldValue.firstName + ' to ' + newValue.firstName + '!');
});

Note that the values being passed as the old and new values will match the key path that we have specified, so in this case they will both be the name object.

Let’s run the code again and change the first name.

Wait a minute, our watcher doesn’t seem to be working! This is because we have to tell Vue to also look for nested values within objects. We can do that by passing an object of options as the third argument to the $watch method. The option we are interested in, is the deep option, which we will set to true.

vm.$watch('person.name', function(newValue, oldValue) {
	alert('The first name was changed from ' + oldValue.firstName + ' to ' + newValue.firstName + '!');
}, { deep: true });

With this, we will see an alert when we change the first name.

However, notice that the old and new values are the same! This is because when mutating an object or array (meaning modifying it without creating a new copy), the old and new values will be the same because Vue does not keep a copy of the previous value. Both arguments are therefore references to the same object or array. So keep that in mind when writing code that relies on accessing the old value of objects or arrays.

But what if you need a more complex expression? In that case, instead of adding an expression as the first argument to the $watch method, you could also pass in a function. This function should simply return the expression that you wish to watch for changes. Let’s see a quick example of watching the counter data property for changes with a function instead.

vm.$watch(
	function() {
		return this.counter;
	},
	function(newValue, oldValue) {
		alert('Counter changed from ' + oldValue + ' to ' + newValue + '!');
	}
);

You could of course add more complex expressions than this. Whenever one of the dependencies within the expression changes, the watcher is fired. This approach supports more advanced use cases and is probably less commonly used.

Okay, now there’s just one more thing I want to show you; how to remove a watcher. Whenever we use the $watch method, it actually returns a function which we can then invoke to stop watching for changes later on in our script. Let’s try this by assigning the return value of the method to a variable.

var unwatch = vm.$watch(
	function() {
		return this.counter;
	},
	function(newValue, oldValue) {
		alert('Counter changed from ' + oldValue + ' to ' + newValue + '!');
	}
);

Just so that we can see the difference, let’s stop watching for changes after five seconds.

setTimeout(function() {
	unwatch();
}, 5000);

When running the code, we initially see the alert when clicking the button. But after five seconds, we no longer see the alert, because the watcher has been removed.

Alright, so that was quite a few things on adding watchers dynamically. This is useful if you need to apply some logic and perhaps add watchers conditionally or something like that. And on a final note, be aware that you can actually watch computed properties too, and not just data properties.

Featured

Learn Vue.js today!

Take an online course and become an Vue.js champion!

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!
Vue.js logo
Author avatar
Bo Andersen

About the Author

I am a back-end web developer with a passion for open source technologies. I have been a PHP developer for many years, and also have experience with Java and Spring Framework. I currently work full time as a lead developer. Apart from that, I also spend time on making online courses, so be sure to check those out!

Leave a Reply

Your e-mail address will not be published.