I want to set a property, by using Vue.set() in Nuxtjs application.
editPost(post) {
Vue.$set(post, 'edit', true)
}
Got error: Vue is not defined
You can just use this instead of Vue.. This references the current vue component inside the method, so it will work same way:
editPost(post) {
this.$set(post, 'edit', true)
}
Sometimes you may want to assign a number of properties to an existing object, for example using Object.assign() or _.extend(). However, new properties added to the object will not trigger changes. In such cases, create a fresh object with properties from both the original object and the mixin object:
// instead of Object.assign(this.someObject, { a: 1, b: 2 })
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
Related
I'm trying to delete a key from an object in a parent component. A child component emits an event (with an item value) back to the parent method that triggers the delete in the parent's data object.
Parent component:
data() {
return {
savedNews: Object
}
},
methods: {
containsKey(obj, key) {
var result = Object.keys(obj).includes(key)
return result
},
handleSaveNews(item) {
if (!this.containsKey(this.savedNews, item.url)) {
this.savedNews = {
[item.url]: item,
...this.savedNews
}
} else {
console.log(this.containsKey(this.savedNews, item.url))
var res = delete(this.savedNews, item.url)
console.log(res)
console.log(this.containsKey(this.savedNews, item.url))
}
}
}
All of the console.logs in the last else statement return true. It's saying that the delete was successful yet the key is still there. How do I delete this key?
From the docs:
Vue cannot detect property addition or deletion
Use this.$delete:
this.$delete(this.savedNews, item.url)
or this.$set (which also should be used for property changes):
this.$set(this.savedNews, item.url, undefined);
Extra info: The $ is a naming convention Vue uses for its built-in methods that are available on each component instance. There are some plugins which opt to follow this pattern too. You can also use built-ins inside other modules if you import Vue and use Vue.delete, for example. You could add your own methods like Vue.prototype.$mymethod = ....
In my root Vue instance, I have an array of objects with some data, which I use to render a set of components. These components have a watcher on the object of data provided to them, which is supposed to make an asynchronous call every time the object is updated.
The problem is that when I update a property of one of the objects in my array, the watcher is not called. It shouldn't fall into any of Vue's caveats because a) I'm not adding a new property, just updating an existing one and b) I'm not mutating the array itself in any way. So why is this happening? And how do I fix it?
My main Vue instance:
let content = new Vue({
el: '#content',
data: {
testData: [
{ name: 'test1', params: {
testParam: 1
} },
{ name: 'test2', params: {
testParam: 1
} },
{ name: 'test3', params: {
testParam: 1
} }
]
}
});
The code which I use to render my components:
<div id="content">
<div v-for="item in testData">
<test-component v-bind="item"></test-component>
</div>
</div>
And my component:
Vue.component('test-component', {
props: {
name: {
type: String,
required: true
},
params: {
type: Object,
required: true
}
},
data: function() {
return { asyncResult: 0 };
},
watch: {
params: function(newParams, oldParams) {
// I use a custom function to compare objects, but that's not the issue since it isn't even being called.
console.log(newParams);
if(!this.compareObjs(newParams, oldParams)) {
// My async call, which mutates asyncResult
}
}
},
template: `
<span>{{ asyncResult }}</span>
`
});
My goal is to mutate the properties of the params property of a given object and trigger the watcher to rerender the corresponding component, but when I try to mutate it directly it doesn't work.
Example (and the way I'd like my component to work):
content.testData[2].params.testParam = 5;
Unfortunately, it doesn't. Using Vue.set doesn't work either:
Vue.set(content.testData[2].params, 'testParam', 5);
The only thing I found which does work is to assign a new object entirely (which is not something I'd like to do every time I have to mutate a property):
content.testData[2].params = Object.assign({}, content.testData[2].params, { testParam: 5 });
I also tried using a deep watcher, as suggested in a similar question, but it didn't work in my case. When I use the deep watcher the function is called, but both newParams and oldParams are always the same object, no matter which value I set to my property.
Is there a solution to this that will allow me to mutate the array items just by setting a property? That would be the most desirable outcome.
First things first.
Using Vue.set isn't going to help. Vue.set is used to set the values of properties that Vue's reactivity system can't track. That includes updating arrays by index or adding new properties to an object but neither of those apply here. You're updating an existing property of a reactive object, so using Vue.set won't do anything more than setting it using =.
Next...
Vue does not take copies of your objects when passing them as props. If you pass an object as a prop then the child component will get a reference to the same object as the parent. A deep watcher will trigger if you update a property within that object but it's still the same object. The old and new values passed to the watcher will be the same object. This is noted in the documentation:
https://v2.vuejs.org/v2/api/#vm-watch
Note: when mutating (rather than replacing) an Object or an Array, the old value will be the same as new value because they reference the same Object/Array. Vue doesn’t keep a copy of the pre-mutate value.
As you've noticed, one solution is to use a totally new object when performing the update. Ultimately, if you want to compare the old and new objects then you have no choice but to make a copy of the object somewhere. Taking a copy when mutating is a perfectly valid choice, but it's not the only option.
Another option would be to use a computed property to create the copy:
new Vue({
el: '#app',
data () {
return {
params: {
name: 'Lisa',
id: 5,
age: 27
}
}
},
computed: {
watchableParams () {
return {...this.params}
}
},
watch: {
watchableParams (newParams, oldParams) {
console.log(newParams, oldParams)
}
}
})
<script src="https://unpkg.com/vue#2.6.10/dist/vue.js"></script>
<div id="app">
<input v-model="params.name">
<input v-model="params.id">
<input v-model="params.age">
</div>
A few notes on this:
The computed property in this example is only creating a shallow copy. If you needed a deep copy it would be more complicated, something like JSON.stringify/JSON.parse might be an option.
The computed property doesn't actually have to copy everything. If you only want to watch a subset of the properties then only copy those.
The watch doesn't need to be deep. The computed property will create dependencies on the properties it uses and if any of them changes it will be recomputed, creating a new object each time. We just need to watch that object.
Vue caches the values of computed properties. When a dependency changes the old value is marked as stale but it isn't immediately discarded, so that it can be passed to watchers.
The key advantage of this approach is where the copying is handled. The code doing the mutating doesn't need to worry about it, the copying is performed by the same component that needs the copy.
As you said, you will need to use deep property in watch.
Using Vue.set you should remounting the entire object inside your array, like:
const newObj = {
name: 'test1',
params: {
testParam: 1,
},
};
Vue.set(yourArray, newObj, yourIndex);
Note you are setting some value inside your array and in this case the array contains objects.
I have standard Vue.js component and I'd like to convert attributes in data property to watcher or in other words I want to construct a watch object based on data property attributes automatically
my idea looks something like this
watch: {
...(() => {
const watchers = {}
Object.keys(this.$data).forEach(key => {
watchers[key] = () => {
new ProductNutrientUpdate(this).run()
}
})
return watchers
})(),
},
the problem with this approach is that this.$data is not constructed yet
maybe there is some way how I can add watchers in created hook for example??
Vue already watches properties of the data object (note if any of these values are themselves objects, I think you need to update the whole object, i.e. change its value to a shallow copy with the desired nested key-values).
Refer to: https://v2.vuejs.org/v2/guide/reactivity.html
You can then use the update lifecycle hook to watch for all changes to data: https://v2.vuejs.org/v2/api/#updated
I was able to resolve a challenge using the following approach
created() {
Object.keys(this.$data).forEach(key => {
this.$watch(key, function() {
// ... some logic to trigger on attribute change
})
})
}
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
}
}
}
I've got a VueJs front end that fetches some data from an API. The app uses vue-router.
The data fetched for one component is similar to the following:
{
name: ...,
email: ...,
order: {
data: {
line_items: [
{
quantity: ...
}
]
}
}
}
The component is instantiated with a data object called info:
data () {
return {
info: {}
}
}
In the beforeRouteEnter hook, the data is fetched by a vue-resource http.get and info is set to the body of the result like this:
vm.info = result.body
When the component renders, the following errors are produced:
TypeError: undefined is not an object (evaluating _vm.order.data.line_items')
In the template, the data is referenced in curly braces as per usual, however, if I just reference info in the template like this:
{{ info }}
it will output all of the data and not complain at all.
What is the correct way to assign a deeply nested data object?
If you are finding #saurabh answer is not working then you may need to check how you are assigning the new values to your object.
Firstly is the data being accidiently set as a string? hence {{ info }} working (or appearing to). May be worth using response.json() to set the data.
If thats not it then the error may be produced as the data you have set is not reactive. As you are assigning a nested object you may need to use different methods to make it reactive, i.e
Vue.set(vm.someObject, 'b', 2)
or
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })
check out: https://v2.vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats
because your response is an object you may want to break out your data into corresponding params, i.e.
data () {
return {
info: {
name: '',
email: '',
order: {},
},
}
}
then you can assign name & email as you expected (info.email = ...).
For info.order you'd use Vue.set:
Vue.set(this.info, 'order', result.body.order)
The actual issue here is a life cycle one. The route guard beforeRouteEnter is called after the component is created so the error is thrown because the data isn’t there when the component tries to access it.
You have to use condition rendering here, which you can easily do with help of Vue directive v-if. It may give error if the data is not populated and you try to access it, so v-if will render that part of HTML only when data is present.
You need to do something like following:
<div v-if="info.order">
<span>
{{ info.order }}
</span>
</div>
In my scenario, I had to use one Vue.set that wrapped an Object.assign:
I'm trying to set state.workouts[state.workoutDate].isDone in a Vuex mutation
toggleWorkout(state, isDone) {
Vue.set(
state.workouts,
state.workoutDate,
Object.assign({}, state.workouts[state.workoutDate], { isDone: isDone })
);
},
Force update object setting a new object with same content
<button #click="my.object.nested.variable = 2; my = Object.assign({}, my);">
or
this.my.object.nested.variable = 2;
this.my = Object.assign({}, this.my);