Module: Vue Data and Methods

Watchers

Vue Data and Methods -> Watchers

Watchers in Vue.js provide a way to react to changes in your data. They allow you to execute a function whenever a specific data property is modified. This is incredibly useful for performing side effects, like making API calls, updating computed properties, or manipulating the DOM, in response to data changes.

Basic Watchers

The most common way to define a watcher is within the watch option of your Vue component.

<template>
  <div>
    <p>Message: {{ message }}</p>
    <button @click="changeMessage">Change Message</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello Vue!'
    }
  },
  methods: {
    changeMessage() {
      this.message = 'New Message!';
    }
  },
  watch: {
    message(newValue, oldValue) {
      console.log(`Message changed from ${oldValue} to ${newValue}`);
      // Perform side effects here, e.g., API call, DOM manipulation
    }
  }
}
</script>

Explanation:

  • watch: The option where you define watchers.
  • message: The name of the data property you want to watch. This must match the property name in your data option.
  • (newValue, oldValue): The handler function that will be executed when message changes. It receives two arguments:
    • newValue: The new value of the message property.
    • oldValue: The previous value of the message property.

Deep Watching

By default, watchers are not deep. This means that if you're watching an object or array, the watcher will only trigger if the object/array itself is replaced, not if its properties are modified. To watch for changes within an object or array, you need to use the deep option.

<template>
  <div>
    <p>Object: {{ myObject }}</p>
    <button @click="updateObject">Update Object</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      myObject: {
        name: 'Initial Name',
        age: 30
      }
    }
  },
  methods: {
    updateObject() {
      this.myObject.age = 31; // Modifying a property, not replacing the object
    }
  },
  watch: {
    myObject: {
      handler(newValue, oldValue) {
        console.log('Object changed:', newValue, oldValue);
      },
      deep: true // Enable deep watching
    }
  }
}
</script>

Explanation:

  • deep: true: This option tells Vue to recursively watch all properties of the myObject. Now, when myObject.age is updated, the watcher will be triggered.

Immediate Execution

Sometimes you want the watcher to run immediately when the component is created, even before the data property changes. You can achieve this using the immediate option.

<template>
  <div>
    <p>Count: {{ count }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0
    }
  },
  watch: {
    count: {
      handler(newValue, oldValue) {
        console.log('Count is now:', newValue);
      },
      immediate: true // Run the handler immediately
    }
  }
}
</script>

Explanation:

  • immediate: true: The handler function will be called once when the component is created, with the initial value of count (which is 0 in this case).

Watcher as an Object

As shown in the examples above, you can define a watcher as an object with handler, deep, and immediate options. This provides more control over the watcher's behavior.

Using $watch Programmatically

You can also create watchers programmatically using the $watch instance method. This is useful when you need to create watchers dynamically.

<template>
  <div>
    <p>Value: {{ value }}</p>
    <button @click="startWatching">Start Watching</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      value: 10
    }
  },
  methods: {
    startWatching() {
      this.$watch('value', (newValue, oldValue) => {
        console.log('Value changed (programmatically):', newValue, oldValue);
      }, {
        immediate: true,
        deep: false // Optional: specify deep watching
      });
    }
  }
}
</script>

Explanation:

  • this.$watch('value', callback, options):
    • 'value': The data property to watch.
    • callback: The function to execute when the property changes.
    • options: An optional object with immediate and deep options.

Unwatching

When using $watch, it's important to unwatch when the component is destroyed to prevent memory leaks. $watch returns an unwatch function.

<template>
  <div>
    <p>Value: {{ value }}</p>
    <button @click="startWatching">Start Watching</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      value: 10
    }
  },
  methods: {
    startWatching() {
      const unwatch = this.$watch('value', (newValue, oldValue) => {
        console.log('Value changed (programmatically):', newValue, oldValue);
      });

      // Unwatch in the beforeDestroy lifecycle hook
      this.$once('hook:beforeDestroy', unwatch);
    }
  }
}
</script>

Key Considerations:

  • Performance: Deep watching can be expensive, especially for large objects. Use it only when necessary.
  • Side Effects: Watchers are best suited for side effects. Avoid complex logic within watchers; consider using computed properties for data transformations.
  • Memory Leaks: Always unwatch programmatically created watchers to prevent memory leaks. Use this.$once('hook:beforeDestroy', unwatch) for a clean unwatch in the beforeDestroy lifecycle hook.
  • Alternatives: Consider using computed properties if you need to derive new data based on existing data. Computed properties are cached and only re-evaluated when their dependencies change, which can be more efficient than watchers in some cases.