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.

Practice this code!
<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/[email protected]"></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.

Practice this code!
<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/[email protected]"></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.

Practice this code!
<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/[email protected]"></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.

Update the previous example's JavaScript with this code!
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.

Update the previous example's JavaScript with this code!
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.

Practice this code!
<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/[email protected]"></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.

Update the previous example's JavaScript to reflect this code!
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

VueJs Directives: Form Binding and Event Handling

Subscribe to the VueNoob Newsletter

[[[ noty.message ]]]

By using this site, you agree that you have read and understand its Privacy Policy.