I have 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.
I have an app where I am 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 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 I have gone wrong here then it would be greatly appreciated, many thanks!
Your 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 your 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: `
List of people:
I have changed the implementation of it to get your problem solved, I made an object to track the old changes and compare it with that. You can use it to solve your 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
vm.$data
, thank you!
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 will not keep an old copy of an object that you mutated.
Had you replaced the object with another one, Vue would have provided you with correct references.
Read the Note
section in the docs. (vm.$watch
)
The component solution and deep-clone solution have their advantages, but also have issues:
Sometimes you want to track changes in abstract data - it doesn't always make sense to build components around that data. Deep-cloning your entire data structure every time you make a change can be very expensive.
I think there's a better way. If you want to watch all items in a list and know which item in the list changed, you can set up custom watchers on every item separately, like so:
var vm = new Vue({
data: {
list: [
{name: 'obj1 to watch'},
{name: 'obj2 to watch'},
],
},
methods: {
handleChange (newVal) {
// Handle changes here!
console.log(newVal);
},
},
created () {
this.list.forEach((val) => {
this.$watch(() => val, this.handleChange, {deep: true});
});
},
});
With this structure, handleChange()
will receive the specific list item that changed - from there you can do any handling you like.
I have also documented a more complex scenario here, in case you are adding/removing items to your list (rather than only manipulating the items already there).
created()
thought? and not every time a sub property on an object with an array changes? I tried this out and it did not log anything to the console when I added it in created @ErikKoopmans
created
just sets up the watchers. After that code runs, any time one of the values in the list changes (like you set this.list[0].name = 'new name'
), the handleChange
code will trigger.
This is what I use to deep watch an object. My requirement was watching the child fields of the object.
new Vue({
el: "#myElement",
data:{
entity: {
properties: []
}
},
watch:{
'entity.properties': {
handler: function (after, before) {
// Changes detected.
},
deep: true
}
}
});
if we have Object or Array of object and we want to watch them in Vuejs or NUXTJS need to use deep: true
in watch
watch: {
'Object.key': {
handler (val) {
console.log(val)
},
deep: true
}
}
watch: {
array: {
handler (val) {
console.log(val)
},
deep: true
}
}
Instead of "watch" I solved the problem with "computed"!
I have not tested this code but i think it should work. Please tell me in the comments if not.
<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}
],
oldVal: {},
peopleComputed: computed({
get(){
this.$data.oldVal = { ...people };
return people;
},
set(val){
// Return the object that changed
var changed = val.filter( function( p, idx ) {
return Object.keys(p).some( function( prop ) {
return p[prop] !== this.$data.oldVal[idx][prop];
})
})
// Log it
console.log(changed)
this.$data.people = val;
}
}),
}
})
</script>
Success story sharing