4

How can I $watch changes to specific properties of a list item? For instance in the below code, I want to know whenever the Done property on any of the TODO list items changes.

I see from the docs that I can watch subproperties of objects, like myObjects.done in the code below, but I am not sure about the syntax for lists.

I should also mention I would prefer to $watch the data instead of putting event handlers in the UI, and function calls in any spot that changes the property

var vm = new Vue({
  el: "#app",
  data: {
    myObject: { done: true },
    todos: [
      { text: "Learn JavaScript", done: false },
      { text: "Learn Vue", done: false },
      { text: "Play around in JSFiddle", done: true },
      { text: "Build something awesome", done: true }
    ]
  },
});

//This works wonderfully on non list items
vm.$watch("myObject.done", function(val)
{
    console.log("myObject.done changed", val);
});

//How do I monitor changes to the done property of any of the todo items?
vm.$watch("todos[*].done", function(val)
{
    console.log("todos.done changed", val);
})

JSFiddle here: http://jsfiddle.net/eywraw8t/376544/

Mr Bell
  • 9,228
  • 18
  • 84
  • 134

3 Answers3

4

With your current approach, you'd have to deep-watch the array and do some heavy computations in order to figure out the changed element. Check this link for the example: Vue - Deep watching an array of objects and calculating the change?

I think the better approach would be using change event handler:

<input type="checkbox" v-model="todo.done" @change="onTodoChange(todo, $event)">

JSFiddle: http://jsfiddle.net/47s0obuc/

Andrius Rimkus
  • 643
  • 5
  • 10
  • 1
    Deep watching definitely sounds unappealing – Mr Bell Sep 18 '18 at 17:00
  • The problem with using the @change attribute is that it only catches changes that are caused by changing the value on that UI element. If something in the code changes the value from elsewhere in the app, you are left in the dark. – Mr Bell Sep 18 '18 at 17:00
  • Makes sense. Well if you only care about the fact whether ANY element has been altered, `deep watch` on the array should do. The hard part is pinpointing the changed element. – Andrius Rimkus Sep 18 '18 at 17:04
1

To watch specific property, I'd create another component for the list item and pass the item as value to watch the changes from that component.

Vue.component("TaskItem", {
    template: `
        <li
            class="task-item"
            :class="{ done: complete }"
        >
            <p>{{ task.description }}</p>
            <input type="checkbox" v-model="complete">
        </li>
    `,
    props: ["task"],
    computed: {
        complete: {
            set(done) {
                this.$emit("complete", this.task, done);
                // we force update to keep checkbox state synced
                // in case if task.done was not toggled by parent component
                this.$forceUpdate();
            },
            get() {
                return this.task.done;
            }
        }
    }
});

new Vue({
    el: "#app",
    template: `
        <div>
            <ul class="task-list">
                <TaskItem
                    v-for="(task, i) in tasks"
                    :key="i"
                    :task="task"
                    @complete="complete"
                />
            </ul>
            <button @click="completeFirstTask">Complete first task</button>
        </div>
    `,
    data() {
        return {
            tasks: [
                { description: "Get milk", done: false },
                { description: "Barber shop", done: true },
                { description: "Fix sleep cycle", done: false }
            ]
        };
    },
    methods: {
        complete(item, done) {
            item.done = done;
        },
        completeFirstTask() {
            this.tasks[0].done = true;
        }
    }
});

https://codesandbox.io/s/wqrp13vp25

Yiin
  • 659
  • 6
  • 9
  • This basically just lets you know if anything in TODOs has changed right? Even if its a property other than Done? – Mr Bell Sep 18 '18 at 17:06
  • This idea of creating a component sounds interesting. Can you elaborate on that? – Mr Bell Sep 18 '18 at 17:21
0

I used this and it works for me.

var vm = new Vue({
  el: "#app",
  data: {
    myObject: { done: true },
    todos: [
      { text: "Learn JavaScript", done: false },
      { text: "Learn Vue", done: false },
      { text: "Play around in JSFiddle", done: true },
      { text: "Build something awesome", done: true }
    ]
  },
  watch:{
  todo: function(val) {
  console.log ("This TODO is Done", val)
  }
});
<template>
  <div class="mainDiv" v-for="(index, todo) from todos">
    <div>{{todo.text}}</div>
    <input type="checkbox" v-model="todo[index].done">
  </div>
</template>
Niaz Estefaie
  • 567
  • 6
  • 21