I need to assign the data variable of type object to a normal variable
const myVue = new Vue({
el: '#myVue',
data: {
vars: {}, // show Form
},
methods: {
assign_vars() {
const new_vars = this.vars;
},
},
});
html
<input type="text" v-model="vars.name" >
I need new_vars to be just like this:
new_vars: { name: 'test' }
but what happens is new_vars is having all the attributes that Vue creates
Vue wraps its observed data in a special object to efficiently observe changes (this is what enables reactivity). In most use cases you won't need to unwrap the object, but in case you do, here are a couple of methods:
const unwrapped = JSON.parse(JSON.stringify(this.vars));
This is the method suggested by Evan You here. It works well even for deeply nested objects, as long as the types can be converted to and from JSON (strings, numbers, booleans, objects, and arrays). If your data contains something else (e.g. dates), you'll need another approach.
const unwrapped = { ...this.vars }
This works well for shallow objects, even if they contain non-primitives.
If you have a deeply nested object containing non-primitive values, you'll need to write a function that recursively unwraps its sub-objects.
u can use toRaw(reactive object goes here) method.
If you want to keep functions, lodash.defaultsDeep({}, vm.$data) is a good idea.
Related
I just noticed an unexpected behaviour and now I don't know if it is normal or not.
I have a component named follows and a child component named follow-list-modal
I'm passing a followList (pagination ) from follows to its child component follow-list-modal
In the follow-list-modal I store the paginated array in the variable members
Follows.vue
<template>
<div>
<follow-list-modal
:follow-list="dataset">
</follow-list-modal>
</div>
</template>
<script>
export default {
props: {
dataset: {
type: Object,
default: {},
},
},
}
</script>
FollowListModal.vue
<template>
<div>
<button #click="fetchMore"> More </button>
</div>
</template>
<script>
export default {
props: {
followList: {
type: Object,
default: {},
},
data() {
return {
members: this.followList.data,
dataset: this.followList,
};
},
methods: {
fetchMore() {
let nextPage = parseInt(this.dataset.current_page) + 1;
axios
.get(this.dataset.path + '?page=' + nextPage)
.then(({ data }) => this.refresh(data))
.catch((error) => console.log(error));
}
},
refresh(paginatedCollection) {
this.dataset = paginatedCollection;
this.members = this.members.concat(...paginatedCollection.data);
},
}
When I click the button More in the follow-list-modal to get more data, I then want to append the new data to the members array.
The unexpected behaviour ( for me at least ). is that if I use push in the refresh method
this.members.push(..paginatedCollection.data);
It appends data not only to members but also to followList which is data that comes from the parent component follows
But if I use concat instead of push, it appends data only to members variable and not to followList
this.members = this.members.concat(..paginatedCollection.data);
Is this behaviour normal ?
I don't get why the followList changes when the members variable changes, I thought that reactivity is one way.
In other words, the members changes when the followList changes, but not the other way around
P.S I don't emit any events from follow-list-modal to follows or change the data of the follows component in any way from the follow-list-modal
In JavaScript, the properties of an Object that are also Objects themselves, are passed by reference, and not by value. Or you might say that they are shallow copied.
Thus, in your example, this.members and this.followList.data are pointing to the same variable in memory.
So, if you mutate this.members, it will mutate this.followList.data as well.
You could avoid this by doing a deep copy of the objects. The easiest method, and arguably the fastest, would be to use JSON.parse(JSON.stringify(obj)), but look at this answer for more examples.
data() {
return {
members: [],
dataset: [],
};
},
created() {
this.members = JSON.parse(JSON.stringify(this.followList.data));
this.dataset = JSON.parse(JSON.stringify(this.followList));
}
You instantiate your data with a direct link to the (initially undefined) property of your prop. This property is a complex entity like an Object (Arrays are Objects), and is thus called via reference. Since members references the same thing in memory as followList.data, when you're calling members, it will follow the reference to the same entity as followList.data. This doesn't have to do with Vue2 reactivity, but here's a link nontheless.
push mutates the array it is called on; it will follow the reference through members and change followList.data, updating its value when called through followList as well. Because the data key is not present on instantiation of the component, Vue can't watch it (just like you need to use Vue.set when adding a new key to a data object).
concat returns a new array of merged elements, and then replaces
the reference in members with the new array. Therefore from this point on you'll
no longer mutate followList.data, even with a push, as the reference has changed to a new entity.
When trying to set your initial members and dataset, I suggest using an initialization method that creates a clone of your followList and writes that to dataset, and running this on the created() or mounted() hook of your component lifecycle. Then create a computed property for members, no need to store followList.data thrice and potentially have dataset and members diverge.
I'm using vue.js extends for the first time. I have a component that extends another and it needs to read the root components data to update the status in its own component.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
So I might not be going about this the right way if the extended component doesn't update when the root does. For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
Is this the expected behaviour and is there a way I can send the updated data down to the extended component?
Sample code:
<a inline-component>
<input type="text" v-model="myArray" />
<button v-on:click="saveData">Save</button>
</a>
<b inline-component>
<div v-if="myArray.length > 0">On Target</div>
</b>
var a = Vue.component('a', {
data: function () {
return {
myArray: [],
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
vm.myArray = response.data;
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
I have a component that extends another and it needs to read the root components data to update the status in its own component.
For the purposes of my answer I'm going to assume that you have two component definitions, A and B, and B extends A. I assume that when you say root you just mean A.
What I'm finding is that the component that extends the other only seems to take a copy of the root's data when it's rendered but if I update a property in the root component it's not updated in the extended component.
Rendering is not really relevant here. The data properties are set up when a component instance is created. Typically rendering will happen just after creation but merging any data happens much earlier in the component life-cycle. Even if the component isn't rendered the data will still be initialised.
No copying takes place. Let's consider a data function on component A:
data () {
return {
myArray: []
}
}
Every time this function is invoked it is going to return a new object, each containing a new array. This is precisely what happens if you create an instance of A directly. For each instance, Vue will call this function and get a new object defining the data. Generally that's what you'd want, rather that having components sharing data.
Now let's consider B. That might define its own data function. When an instance of B is created Vue will call the data function for both A and B and then merge the objects. No copying takes place, just merging. If you want to know more about how Vue handles merging in general see the documentation but for data the strategy is pretty simple. Properties from both objects will be combined with B taking precedence over A if there's a clash of property names. There is no recursive merging of properties.
So the idea of updating 'a property in the root component' is not particularly well-defined. You might be thinking of it as a bit like a prototype chain, where modifying a superclass would impact the subclass, but that isn't what's going on here. The data functions are invoked when the component is created and that's that. There isn't a lasting link back to the component definition like there is with a prototype chain.
If you really want all your component instances to share the same data value then it can be done, you just need to make sure that the data function is returning the same object/array every time. e.g.
const myArray = []
export default {
name: 'A',
data () {
return {
myArray
}
}
}
Written this way all instances of A will share the same array for myArray. So long as B doesn't define it's own value for myArray it will share it too.
For example I want to check the length of an array on the root component and update another data value. It updates the value on the root but not on the extended component.
I'm struggling a bit to understand what that means. It seems there are lots of assumptions about things being shared, single instances here. It's not entirely clear how you update the 'root' given it's a component definition and not a component instance.
If possible you should use a computed property for this. That would be inherited by B. Each instance of A (or B) would have their own value for this computed property, which might be a little wasteful if they're all going to be the same, but it's probably still the best way to go.
You could in theory use a watch. That should be inherited too but keep in mind it would be manipulating values for that particular instance.
Reading between the lines a little, if you wanted to update something on the 'root' so that it magically appeared in the subcomponents you could use the same shared reference-type trickery that I demonstrated earlier for myArray. You may need to be careful with how you update it though. If, for example, you used a watch you might find the you end up updating the same object many times, once for each instance of the component.
Update:
Based on the code you've posted it could be made to work something like this:
var myArray = [];
var a = Vue.component('a', {
data: function () {
return {
myArray: myArray // Note: using the same, shared array
}
},
methods: {
saveData : function(){
var vm = this;
axios.post('/save', {
})
.then(function (response) {
// Note: Updating the array, not replacing it
var myArray = vm.myArray;
myArray.splice(0, myArray.length);
myArray.push.apply(myArray, response.data);
})
.catch(function (error) {
console.log(error);
});
},
}
});
Vue.component('b', {
extends: a,
});
Your example didn't include any ES6 so I've refrained from using it but it would be a bit simpler if that were available.
The example above works by sharing the same array between all instances of the component and then mutating that instance. Assigning a new array to that property won't work as it would only update that particular component instance.
However, all that said, this is increasingly looking like a case where you should give up on trickery and just use the Vuex store instead.
I know that in order for an object or array to be reactive in Vue its properties have to be defined on the root data structure.
What's the best way to add an array of objects to a pre-existing variable defined on the root data structure, and make every property of every element in that array reactive?
I have tried looping through the array and adding each to the root data model, ie:
these_terms.forEach(function(term, idx) {
term.selected = false;
Vue.set(vm.game.set,idx,term);
});
However, Vue does still not respond to the "term.selected" property when it is later changed.
Is there a better way of achieving my aim, or do I need to resort to $forceUpdate? (the manual says that in 99% of cases using $forceUpdate, you're doing something wrong, hence this post)
On your parent component, do the following:
Make a data attribute with a empty array starting out
Make a button that calls a method
In that method, push to the empty array.
Example of step 3
methods: {
_addGroup: function() {
let result = {
id: this.wizardGroups.length + 1,
name: '',
};
this.wizardGroups.push(result);
},
If you need to append additional properties afterwards, you can loop through the array of objects and apply Vue.set() as well
Sorry if I understand it wrong but why dont you import the array and bring it into a Vue Data Variable?
import xx from "xxxx.js"
export default {
data() {
return {
y: xx
}
}
}
Why nested fields of literal objects bound to a component’s property do not get reactive and observed?
Example:
<my-comp :my-prop="{ my: { literal: { object: { with: { nested: { field: ‘not reactive’ }}}}}}"></my-prop>
When you do this inside my-comp:
created() {
console.log(this); // you can see in the Chrome console that nested fields of this.myProp do not get reactive (i.e. no getters)
}
I know that I can play around it, but I am just asking why not?
Is there a neat way to make it reactive?
Thank you!
This is because the way "HTML" templates are rendered.
When Vue "compiles" your html, it basicly creates the following "computed" function
render(createElement) { /* `createElement` is also known as `h` in the Vue world */
return createElement('my-comp', {
props: {
myProp: { my: { literal: { object: { with: { nested: { field: ‘not reactive’ }}}}}},
},
);
},
Since the full flow of the value of myProp never passes through the data function, nor through the return result of a normal computed function, it never gets marked for reactivity.
While it might sound weird that Vue marks the return result of all "computed" functions, expect the special render function reactive, it actually makes sense if looking at the design idea of Vue, namely props in, and events out. This not marking of reactivity gives us a performance boost here, compared to marking it reactive.
If you still require it to be reactive (and thus want to go against the core principles of Vue (and thus making your application harder to understand) ), you can do it, by storing the value inside the data section of your component, or by returning it from a computed property.
My component would like to add a new reactive-array field to the SST (vuex). I tried in beforeCreate hook, but the added array is not reactive; it's just a plain JS array.
Note that this is not the same as adding/removing elements from an existing array created at the Vue's initialization time. Such arrays are "wrapped" and become reactive as expected, mindful of "Array Change Detection" gotchas.
In my case, I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
Have a look at Reactivity in Depth - Change Detection Caveats:
Change Detection Caveats
Due to the limitations of modern JavaScript, Vue cannot detect property
addition or deletion.
Since Vue performs the getter/setter conversion process during
instance initialization, a property must be present in the data object
in order for Vue to convert it and make it reactive.
But you say you are adding an array dynamically:
I'm trying to dynamically add an entirely new field of array type to the SST and make it reactive at the same time. Possible?
From the docs (bold is mine):
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. However, it’s possible to add reactive properties to a nested object using the Vue.set(object, key, value) method:
Vue.set(vm.someObject, 'myArrayName', [1,2,3]);
Which should help you making your array reactive.
I see two problems here:
add dynamically array using vuex.
add dynamically element to this array and render this element.
I've initiate array if not exist in add method because when I'm receiving data from server myArray is not exist.
My solutuion below:
myVuexArray.js
import Vue from 'vue'
const state = {
myObject: {
myArray: [],
}
}
const getters = {
getMyArray: state => {
return state.myObject.myArray;
}
}
const mutations = {
addElementToArray(state, value) {
if (state.myObject.myArray === null || state.myObject.myArray === undefined || state.myObject.myArray === '') {
// initiate array
state.myObject.myArray = [];
}
// add new element to array
Vue.set(
state.myObject.myArray,
state.myObject.myArray.length,
value
);
// creates a new array everytime this solves the reactivity issue
Vue.set(state, 'myObject.myArray', state.myObject.myArray);
return state.myObject.myArray;
},
removeElementFromArray(state, index) {
state.myObject.myArray.splice(index, 1);
}
}
export default {
state,
mutations,
getters
}
Best regards
Dynamic module registration could help you to achieve this :
https://vuex.vuejs.org/en/modules.html
This would allow you to dynamically register a new module containing your array field in the beforeCreate hook.