VueJs Watchers: Responding to data changes in Vue
Created on April 7, 2022.
We have learned how to update data in Vue in response to changes in relative dependencies in computed properties, we have also learned how to perform changes to data and DOM updates via methods. In this post, we will learn how to respond to data changes in Vue in a way that is similar but a bit different from the earlier two.
Vue Watchers
"Watch"ers are another option in Vue as “computed” properties are. We declare watchers by adding a “watch” property inside the Object we pass inside Vue’s createApp() function, then passing the names of variables we intend to watch for changes as subsequent functions names inside this property.
Let’s see an example.
<html>
<div id="app">
<h2>Login Form</h2>
<form>
<label for="age-select">How old are you? </label>
<select id="age-select" v-model="myAge">
<option v-for="(num, key) in 30" :value="num" :key="num">{{ num }} yrs</option>
</select>
<hr>
<p v-show="myAge > 0">{{ comment }}</p>
</form>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
myAge: 0,
comment: ""
}
},
watch: {
myAge(newVal, oldVal){
this.comment = newVal >= 18 ? "You are too old to be lazy." : "The future is your canvas.";
console.log(newVal, oldVal);
}
}
}).mount("#app");
</script>
</html>
Vue watchers give us access to both the old and new states of a variable as demonstrated in the ** myAge** watcher in the example above. Open your browser’s console to see the two values logged when you change the selection.
Within watchers, we can access the data properties and functions inside this
; make data mutations, invoke function calls, or update the DOM when certain conditions on our states are met.
Let’s see a function invoking watcher example.
<html>
<div id="app">
<h2>Login Form</h2>
<form>
<label for="age-select">Fruit Search</label>
<br>
<input type="text" v-model="fruit" style="padding: 3; font-size: 20">
<br>
<p v-if="suggestion">Are you looking for {{ suggestion }}?</p>
</form>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
fruit: "",
database: ['Oranges', 'Pineapples', 'Papayas', 'Tomatoes', 'Mangoes'],
suggestion: ""
}
},
watch: {
fruit(newVal, oldVal){
console.log(newVal, oldVal);
if(newVal.length >= 3){
this.queryFruitDB();
}
}
},
methods: {
queryFruitDB(){
this.suggestion = "";
const fruitSearch = () => {
let allFruits = this.database
.forEach(x => {
if((x.toLowerCase()).includes(this.fruit.toLowerCase())){
this.suggestion = x;
}
})
}
setTimeout(fruitSearch, 1000);
}
}
}).mount("#app");
</script>
</html>
The watcher in the example above is supposed to invoke the queryFruitDB() method when the text input - fruit’s length is greater or equal to 3. When that condition is satisfied, the queryFruitDB is called providing us useful suggestions to the fruits we are searching for.
Performing Deep watches
Watches are deep by default, they only react to state changes when the values being watched have been assigned a new value.
Let’s observe the following example.
<html>
<div id="app">
<h2>Sort By Scores</h2>
<br>
<p v-for="(student, key) in students" :key="key">
Name: {{ student.name }} <br>
<input type="number" v-model.number="student.score">
</p>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
students: [{
name: "Noob",
score: 90
},{
name: "John",
score: 100
}]
}
},
watch: {
students(){
this.sortStudents();
}
},
methods: {
sortStudents(){
const sortAll = () => {
this.students.sort((a, b) => b.score - a.score);
}
setTimeout(sortAll, 2000)
}
},
}).mount("#app");
</script>
</html>
In the above example, we are trying to sort the two students based on their score values in descending order. We are listening to the students’ Array intending to update the order presented when the score values are updated.
You’ll discover that when you change the score values no sorting takes place. This is because, just as discussed above, Vue watchers will only change when the whole Array variable is changed.
To change this behavior and listen to deeply nested data, we need to initiate the watcher as an Object containing a handler function and adding a deep property with the value of true
.
Replace the watcher in the example above with the following code to fix the problem.
watch: {
students: {
handler(){
this.sortStudents();
},
deep: true
}
},
NOTE: Deep watchers observe all nested properties in watched Objects, hence can be expensive when used in larger data structures. Only use them where necessary
Performing Eager Watching.
Watchers are lazy by default, they react to data changes beyond the initial state. To execute watchers immediately after they are created for the sake of checking or setting up initial data, we need to setup watchers with the handler function and pass an immediate: true
option.
In the previous example, the students aren’t sorted to our terms initially since a student with a lower score is ranked first followed by one with a higher score. Proper sorting is only performed after subsequent score changes.
We can fix that initial issue by applying eager watching as follows.
watch: {
students: {
handler(){
this.sortStudents();
},
deep: true,
immediate: true
}
},
Creating Watchers Conditionally
Vue watchers can be created conditionally using the $watch
instance method, this is useful when we want to use watchers when certain conditions apply and stop using them afterward.
Let’s see an example of this.
<html>
<div id="app">
<label for="makes">Makes: </label>
<select id="makes" v-model="selectedCarMake">
<option v-for="(make, key) in allMakes" :key="key">{{ make }}</option>
</select>
<br>
<label for="models">Models: </label>
<select id="models" v-model="selectedCarModel">
<option v-for="(model, key) in modelsToChooseFrom" :key="key">{{ model }}</option>
</select>
</div>
<script src="https://unpkg.com/vue@3"></script>
<script>
let app = Vue.createApp({
data() {
return {
allMakes: ["Mazda", "Hyundai"],
selectedCarMake: "",
selectedCarModel: "",
mazdaModels: ["Mazda 3", "Mazda CX-5"],
hyundaiModels: ["Santa Fe", "Biante"],
modelsToChooseFrom: [],
}
},
mounted(){
this.$watch('selectedCarMake', (newVal) => {
if(newVal){
this.modelsToChooseFrom = newVal === "Mazda" ? this.mazdaModels : this.hyundaiModels;
}
})
}
}).mount("#app");
</script>
</html>
In the example above, we initiate a selectedCarMake watcher inside Vue’s mounted hook. It checks the new car make value and populates the modelsToChooseFrom Array accordingly. Try selecting the car model to see the watcher in action.
As cited above, the conditional creation of watchers offers us a feature that the option method doesn’t, the ability to stop watching on prompt.
Take the above example, if we wanted to stop watching selectedCarMake after a car make is selected, all we need to do is assign the function returned by the $watch
instance method to a variable, then call that function when we want to stop the watcher. Let’s modify our code and do just that.
Update the mounted hook in the previous example to reflect the following.
mounted(){
let unwatch = this.$watch('selectedCarMake', (newVal) => {
if(newVal){
this.modelsToChooseFrom = newVal === "Mazda" ? this.mazdaModels : this.hyundaiModels;
unwatch();
}
})
}
Now, the second select options stop updating after the first select choice is made.
A specific case of watchers regarding updating the DOM will be covered after having understood VueJs lifecycle hooks on the next post. For now, that’s about it on Vue watchers.
Next
VueJs Lifecycle Hooks: A Look Into the Lifecycle of a Vue App
Previous