Watch elements of simple array - javascript

Vue dont see changes in simple array items.
I am learning Vue.js and have problem with watcher.
Namely i am trying to watch changes in array, and change one data value.
Every time i add a new item and change or delete an existing item, I want to change the value.
data() {
return {
change: false,
array: ['one','two','three','four']
}
},
watch:{
array:{
deep:true,
handler(){
this.change = true;
}
}
}
Vue just see when array length is changed but not particular element.

You can watch nested values in an object as shown in the docs use a dot-like-string notation.
var vm = new Vue({
data: {
e: {
f: {
g: 5
}
}
},
watch: {
// watch vm.e.f's value: {g: 5}
'e.f': function (val, oldVal) { /* ... */ }
}
})
To my knowledge you CANNOT do this with arrays (i.e. array[0]) because the reference my shift or be removed. I think the best way to do what you want is to compare the newValue and oldValue in the watcher handler function if the whole array changes.
// from vuejs docs
watch: {
// whenever question changes, this function will run
question: function (newQuestion, oldQuestion) {
this.answer = 'Waiting for you to stop typing...'
this.debouncedGetAnswer()
}
},

Related

watch: true for nested objects - NuxtJS (Vue), latest version

I have a form with many fields attached to a data - this.myData:
data: function() {
return {
isDataChanged: false,
myData: {},
myChangedData: {
default: '',
default1: {},
default2: []
},
}
},
myData is populated from a response from the server and it populates the form values.
myChangedData is for the new values, which are changed v-on:input="onChangeMyData($event, 'default')":
onChangeMyData(e, name, required = false){
const val = e.target.value.trim();
this.myChangedData[name] = val;
console.log(this.myChangedData)
this.checkIsmyDataChanged();
},
I can use the same method, providing a key as a second param. With the method checkIsmyDataChanged I am checking is it changed some field in the form. This method loops through myChangedData and compares its properties with changedData and if there is a difference this.isDataChanged = true.
The problem is that, I have a complicated structure of mydata/mydatachanged. default1 has objects in it and default1 is an array of objects. This means that, I can't use onChangeMyData, but other methods with different checks (validations) and now I need to call in all of them this.checkIsmyDataChanged();.
I created a watch for myChangedData:
watch:{
myChangedData: {
handler: function (newVal) {
console.log('change')
},
deep: true
},
},
, but it doesn't execute on change data
Did you try with Vue.set ? Source
Change this.myChangedData[name] = val; to
this.$set(this.myChangedData, 'name', val)
Thanks to that, the modification on the object should be detected and execute the watcher.

vuejs watch multidimensional array with change info

I have an Array "contracts" in which there are arrays of each contract information.
On the editing page I'd like to have the Info which contract was edited.
new Vue({
data(): {
return {
contracts: [
{name:"a",changed:false},{name:"b",changed:false}
]
}
},
watch: {
contracts: {
handler: function(val) {
this.contracts[x].changed=true;
},
deep=true
}
}
});
I simply want to find "x", the Index of the changed Array. The number of contracts is variable so watching every single one wouldn´t work.

Prevent prop from overwriting the data

I'm new to vue.js and struggling with the following scenario.
I send an array filled with objects via props to my router-view.
Inside one of my router-view components I use this array in multiple functions, reference it with 'this.data' and safe it inside the functions in a new variable so I don't overwrite the actual prop data.
However the functions overwrite the original prop data and manipulate the data of the prop.
Here is an abstract example of my question:
App.vue
<template>
<div>
<router-view :data='data'></router-view>
</div>
</template>
<script>
export default {
data: function() {
return {
data: [],
};
},
created: function() {
this.getData();
},
methods: {
getData: function() {
this.data = // array of objects
},
}
route component:
<script>
export default {
props: {
data: Array,
},
data: function() {
return {
newData1 = [],
newData2 = [],
}
}
created: function() {
this.useData1();
this.useData2();
},
methods: {
useData1: function() {
let localData = this.data;
// do something with 'localData'
this.newData1 = localData;
}
useData2: function() {
let localData = this.data;
// do something with 'localData'
this.newData2 = localData;
}
}
}
</script>
The 'localData' in useData2 is manipulated from changes in useData1, whereby I don't overwrite the data prop.
Why do I overwrite the prop and how can i prevent it?
The problem you're experiencing a side effect of copying this.data by reference, rather than value.
The solution is to use a technique commonly referred to as cloning. Arrays can typically be cloned using spread syntax or Array.from().
See below for a practical example.
// Methods.
methods: {
// Use Data 1.
useData1: function() {
this.newData1 = [...this.data]
},
// Use Data 2.
useData2: function() {
this.newData2 = Array.from(this.data)
}
}
#Arman Charan is right on his answer. Object and arrays are not primitive types but reference.
There is an awesome video explanation here => JavaScript - Reference vs Primitive Values/ Types
So for reference types you first have to clone it on another variable and later modify this variable without the changes affecting the original data.
However for nested arrays and objects in high level the spead and Array.from will not work.
If you are using Lodash you can use _.cloneDeep() to clone an array or an object safely.
I like functional programming and I use Lodash which I strongly recommend.
So you can do:
let original_reference_type = [{ id:1 }, { id: 2 }]
let clone_original = _.cloneDeep(original_reference_type)
clone_original[0].id = "updated"
console.log(original_reference_type) //[{ id:1 }, { id: 2 }] => will not change
console.log(clone_original) // [{ id: "updated" }, { id: 2 }]
Suggestion: For simple arrays and objects use:
Objects:
let clone_original_data = {...original_data} or
let clone_original_data = Object.assign({}, original_data)
Arrays:
let clone_original_data = [...original_data] or
let clonse_original_data = original_data.slice()
For complex and high nested arrays or Objects go with Lodash's _.cloneDeep()
I think this is most readable, "declarative" way:
First, install lodash npm i lodash. Then import desired function, not the whole library, and initialize your data with array from props.
<script>
import cloneDeep from 'lodash/cloneDeep'
export default {
props: {
data: Array
},
data () {
return {
// initialize once / non reactive
newData1: cloneDeep(this.data),
newData2: cloneDeep(this.data)
}
}
}
</script>

Revert changes to array in Vue

I'm trying to have a component which can change some elements in it. In reality, a change will be like swapping the object in a given position. I did some POC and tried to do the reverting method to be able to leave it how it was before.
export default {
name: 'Landing',
data () {
return {
items: [
{
id: 1,
category: 'Something something'
},
{
id: 2,
category: 'Something something'
}
]
};
},
created() {
this.originals = this.items.slice(0);
},
methods: {
change() {
this.items[0].category = 'Changed';
},
revert() {
// ??
}
}
};
I've tried a couple of things especially after reading this: https://vuejs.org/2016/02/06/common-gotchas/#Why-isn%E2%80%99t-the-DOM-updating
while (this.snacks.length) {
this.items.pop();
}
this.originals.slice(0).forEach(o => this.items.push(o));
But it doesn't work. If I delete the pushing part, I get an empty list correctly but if I try somehow to push it back, it won't work.
What am I missing here?
If you give a working fiddle I can show you what happened.
Basically, because you are modifying the same array object. this.originals refers to the same array as this.items
Slice returns a shallow copy of the object. You should either take a deep copy or your revert should be the one initializing the object.
this.items.pop();
will remove the items from this.originals as well.

How to observe a change on any property in an array or object?

I want to do something like this
Polymer('bc-datalist', {
listitems: [
{name: 'item1'},
{name: 'item2'}
],
observe: {
'listitems': 'change'
},
change: function () {
// do something
}
});
This doesn't work, so my work around is to do something like this:
Polymer('bc-datalist', {
listitems: {
1:{name: 'item1'},
2:{name: 'item2'}
},
observe: {
'listitems.1.name': 'change',
'listitems.2.name': 'change'
},
change: function () {
// do something
}
});
Is there a way of registering a callback when a object/array item has changed?
Correct me if I'm wrong, but it would appear that there's a typo in your first example. Where you mean to reference listitems as the object to observe, you reference listitem instead. Changing this to listitems would make the case for normal top-level properties work:
<polymer-element name="test-el">
<template>
<button on-click="{{clickHandler}}">Click me</button>
</template>
<script>
Polymer('test-el', {
listitems: [
{name: 'item1'},
{name: 'item2'}
],
observe: {
'listitems': 'change'
},
change: function () {
// do something
alert('listitems changed!');
},
clickHandler: function(){
this.listitems.push({ name: 'item3'});
}
});
</script>
</polymer-element>
Onto your question: Polymer does not call the propertyNameChanged callback for properties included in an observe block to the best of my knowledge. This means you will need to specify the exact nested object path (a.b.c) for what you are trying to observe or manually setup the relevant type of Path/Compound observer manually: https://github.com/Polymer/observe-js
Polymer's observe-js library has support for path observation into an object as well as array observation. The former, can be setup similar to what you have (see 2nd example in the docs).
Polymer('x-element', {
observe: {
'a.b.c': 'validateSubPath'
},
ready: function() {
this.a = {
b: {
c: 'exists'
}
};
},
validateSubPath: function(oldValue, newValue) {
var value = Path.get('a.b.c').getValueFrom(this);
// oldValue == undefined
// newValue == value == this.a.b.c === 'exists'
}
});
I'm checking on ArrayObserver support. Will update this post when I know more.
If you have an array of objects and would like to observe changes made to any of those objects' properties, you can try a custom element, <observe-array-items>. It simplifies the process of creating individual PathObservers for all of the objects in the array, and maintains them as items are added/removed from the list.
In your example, you could do something like
<polymer-element name="test-el">
<template>
<observe-array-items array="{{listitems}}"
path="name"
on-array-item-changed="{{handleNameChanged}}">
</observe-array-items>
</template>
<script>
Polymer({
listitems: [
{name: 'item1'},
{name: 'item2'}
],
handleNameChanged: function (e) {
// This will be called whenever the 'name' property
// of an existing object in listitems changes.
// e.detail.oldValue and e.detail.newValue will
// contain the old and current values
// e.detail.index will correspond to the index of the object in the array.
},
listitemsChanged: function() {
// This will be called whenever an object is added or removed from listitems
}
});
</script>
</polymer-element>

Categories

Resources