there is an array called people that contains objects as follows:
Before
[
{id: 0, name: ‘Bob’, age: 27},
{id: 1, name: ‘Frank’, age: 32},
{id: 2, name: ‘Joe’, age: 38}
]
It can change:
After
[
{id: 0, name: ‘Bob’, age: 27},
{id: 1, name: ‘Frank’, age: 33},
{id: 2, name: ‘Joe’, age: 38}
]
Notice Frank just turned 33.
there is an app where when trying to watch the people array and when any of the values changes then log the change:
<style>
input {
display: block;
}
</style>
<div id=”app”>
<input type=”text” v-for=”(person, index) in people” v-model=”people[index].age” />
</div>
<script>
new Vue({
el: ‘#app’,
data: {
people: [
{id: 0, name: ‘Bob’, age: 27},
{id: 1, name: ‘Frank’, age: 32},
{id: 2, name: ‘Joe’, age: 38}
]
},
watch: {
people: {
handler: function (val, oldVal) {
// Return the object that changed
var changed = val.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
return p[prop] !== oldVal[idx][prop];
})
})
// Log it
console.log(changed)
},
deep: true
}
}
})
</script>
I based this on the question that I asked yesterday about array comparisons and selected the quickest working answer.
So, at this point I expect to see a result of: { id: 1, name: ‘Frank’, age: 33 }
But all I get back in the console is (bearing in mind i had it in a component):
[vue.js warn]: Error in watcher “people”
(found in anonymous component - use the “name” option for better debugging messages.)
And in the codepen that I made, the result is an empty array and not the changed object that changed which would be what I expected.
If anyone could suggest why this is happening or where there is gone wrong here then it would be greatly appreciated, many thanks!
Solution :
the comparison function between old value and new value is having some issue. It is better not to complicate things so much, as it will increase the debugging effort later. You should keep it simple.
The best way is to create a person-component and watch every person separately inside its own component, as shown below:
<person-component :person=”person” v-for=”person in people”></person-component>
Please find below a working example for watching inside person component. If you want to handle it on parent side, you may use $emit to send an event upwards, containing the id of modified person.
Vue.component(‘person-component’, {
props: [“person”],
template: `
<div class=”person”>
{person.name}
<input type=’text’ v-model=’person.age’/>
</div>`,
watch: {
person: {
handler: function(newValue) {
console.log(“Person with ID:” + newValue.id + “ modified”)
console.log(“New age: “ + newValue.age)
},
deep: true
}
}
});
new Vue({
el: ‘#app’,
data: {
people: [
{id: 0, name: ‘Bob’, age: 27},
{id: 1, name: ‘Frank’, age: 32},
{id: 2, name: ‘Joe’, age: 38}
]
}
});
<script src=”https://unpkg.com/vue@2.1.5/dist/vue.js"></script>
<body>
<div id=”app”>
<p>List of people:</p>
<person-component :person=”person” v-for=”person in people”></person-component>
</div>
</body>
Solution 2:
there is changed the implementation of it to get the problem solved, I made an object to track the old changes and compare it with that. You can use it to solve the issue.
Here I created a method, in which the old value will be stored in a separate variable and, which then will be used in a watch.
new Vue({
methods: {
setValue: function() {
this.$data.oldPeople = _.cloneDeep(this.$data.people);
},
},
mounted() {
this.setValue();
},
el: ‘#app’,
data: {
people: [
{id: 0, name: ‘Bob’, age: 27},
{id: 1, name: ‘Frank’, age: 32},
{id: 2, name: ‘Joe’, age: 38}
],
oldPeople: []
},
watch: {
people: {
handler: function (after, before) {
// Return the object that changed
var vm = this;
let changed = after.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
return p[prop] !== vm.$data.oldPeople[idx][prop];
})
})
// Log it
vm.setValue();
console.log(changed)
},
deep: true,
}
}
})
See the updated codepen
Solution 3:
It is well defined behaviour. You cannot get the old value for a mutated object. That’s because both the newVal and oldVal refer to the same object. vue.js will not keep an old copy of an object that you mutated.
Had you replaced the object with another one, vue.js would have provided you with correct references.
Read the Note section in the docs. (vm.$watch)
More on this here and here.