What's the right way to store complex objects with methods in Vue? For example, the following object:
const behavior = {
onClick() {
console.log('click')
},
onDoubleClick() {
console.log('double click');
},
onMounted() {
console.log('mounted')
},
beforeMounted() {
console.log('before mounted')
},
// and so on
}
It's needed to pass to my custom component:
<custom-component :behavior="behavior"/>
It's need to note: it's not the appropriate approach to pass each method through props, because behavior might change dynamically.
You should probably use mixins for this. Check out the docs.
Related
I have a Vuex store where I have a getter which works correctly and I can see the changes on the state. But if I call this getter as computed property in component it does not work. The value is still the same.
The store code looks like:
mutations: {
UPDATE_SERVER_FILTERS(state, payload) {
this._vm.$set(state, 'serverFilters', payload);
//state.serverFilters = payload; // Both patterns work
},
getters: {
serverFilters(state) {
return state.serverFilters; // This works fine
}
}
}
The component code:
computed: {
serverFilters() {
return this.$store.getters[this.storeName + '/serverFilters'];
},
}
Here is JSFiddle example https://jsfiddle.net/camo/je0gw9t3/4/ which works fine. And it is a problem cause in my project it does not work. I am prepared to die...
How can I solve it?
In the most bottom part:
new Vue({
store,
el: '#example',
data() {
return {};
},
computed: {},
methods: {
changeFilters() {
this.$store.dispatch(this.storeName + '/updateFilters');
// ^^^^^^^^^^^^^^ there is no storeName
},
},
});
The changeFilters method. You are using this.storeName, but there is no this.storeName! Just like the Child component, add storeName: 'a' to the data() then it should work.
https://jsfiddle.net/4yfv3w87/
Here is the debug process for your reference:
First open the Vue Devtools and switch to the timeline tab. And just click the button, you will see that there is no action is being fired. So the problem must be the one who dispatches the action. And then you will notice that the root component doesn't have a storeName.
So don't panic, just try to trace the code. It will only take a few minutes to find out the issue!
Computed properties might have problem to make an observer reference from returned value out of function. Instead of chaining getters and computed properties, why you don't use just getters or computed properties ? In my opinion, it's a bad practice to use them both, and I can't imagine a situation you need it. So if you need filter operations in many components, just make a getter and use getter in components instead of computed properties.
If you really want to chain them, try this:
new Vue({
store,
el: '#example',
data() {
return {
storeName: 'a'
}
},
computed: {
filters() {
get() {
return this.$store.getters[`${this.storeName}/getFilters`];
}
set(newValue) {
this.$store.dispatch(this.storeName + '/updateFilters');
}
},
},
})
Comment please if someone check it. I don't know are it works.
I have trouble accessing the Vue component. It is a chart which uses computed properties, which are reactive.
In the documentation I found an example how to "reflow" a the chart, which works fine. However I want to trigger this behaviour whenever the state changes.
mounted() {
this.isMounted = true;
this.$store.watch(state => {
if (state.lineData.data.length > 0) {
this.reflow(); // ### Want to access the reflow method here
// this.$computed.chartOptions.chart desired something like this
}
});
},
computed: {
chartOptions() {
var ref = this;
return {
chart: {
map: worldMap,
height: "620px",
events: {
click: function() {
console.log(this);
this.reflow(); ### This works
}
}
},
}
}
Is there a way to access the computed property from the mounted method or am I doing here something fundamentally wrong? Is there a better way to do this? The click event from the highcharts API is reserved and does not allow me to use custom methods :-/
I found a (for me) good solution:
import Highcharts from "highcharts"; // access to all highcharts components
Highcharts.charts[0].reflow(); // select the correct one an reflow it
I have a component that receives an object as prop, like this:
props: ['propObject']
Then, there's a default object defined (I use VueX, so it's actually defined as a $store getter, but to make it simpler, let's say it's defined in the data method) in the data:
data() {
return {
dataObject: {defaultValueA: 1, defaultValueB: 2}
}
}
And I'd like to have a computed property that would behavior like this:
computed: {
computedObject() {
return Object.values(this.propObject).length > 0 ? this.propObject : this.dataObject;
}
}
However, I know this is not possible because Vue watchers don't watch for changes in the key/value pairs of an object.
I have tried to go with a watched property, like this:
props: ['propObject'],
data() {
return {
object: {},
defaultObject: {}
}
},
watch: {
propObject: {
handler: function() {
this.setComputedObject();
},
deep: true
}
},
methods: {
setComputedObject() {
this.object = Object.values(this.propObject).length > 0 ? this.propObject : this.defaultObject;
}
},
mounted() {
this.setComputedObject();
}
However, the watcher handler is not being called at all when the propObject changes, but if I call it directly via console, it works. Is there any way that I can make the computedObject become reactive?
you need to use Vue.set/vm.$set where you change the props (in source component)
for example
changeProp(){
this.$set(propObject,'newprop','newval');
}
and then just you regualr compouted in the target component (the component which receive the prop)
source : https://v2.vuejs.org/v2/guide/list.html#Object-Change-Detection-Caveats
Please take a look at this not-working pseudo code:
Vue.component('child', {
props: [],
template: '<div><input v-model="text"></div>',
data: function() {
return {child-text: ""}
}
})
Vue.component('parent', {
template: '<h1> {{text}} </h1>'
data: function() {
return {parent-text: ""}
}
})
What is the most elegant way to fix this code that whenever the user changes the content of input box in child component, then the variable child-text in child component and the variable parent-text in parent component will change automatically? I also want that if the variable child-text and/or parent-text change then the content of input box will change respectively?
I solved this with my own little data store, its a very simple approach but works good enough for me without the necessity to dive into Vuex.
First, I create my data store somewhere before initializing anything else.
window.globalData = new Vue({
data: {
$store: {}
},
});
After that, I add a global Mixin that allows to get and set data to the global storage.
Vue.mixin({
computed: {
$store: {
get: function () { return window.globalData.$data.$store },
set: function (newData) { window.globalData.$data.$store = newData; }
}
}
});
Then, every component can access the data storage by this.$store. You can check a working example here:
https://codesandbox.io/s/62wvro7083
I use beforeCreate and beforeDestroy hooks in order to add classes to body. In some cases I need to add classes, in some not.
So I have such code in each component which requires this functionality:
beforeCreate() {
document.body.classList.add('has-background')
},
beforeDestroy() {
document.body.classList.remove('has-background')
}
The problem is that if I navigate from one route to another, say from A component to B component, the beforeCreate of the B component executed first, and then beforeDestroy of the A component, which removes the has-background class.
How can I solve the issue?
Try using nextTick()
beforeCreate() {
this.$nextTick().then(() => document.body.classList.add('has-background'))
},
Edit:
I also suggest to use created() rather than beforeCreated(). But to achieve the best behavior, it is best to use mounted()
I have the same issue on my project and I have applied something like this.
methods: {
toggleBodyClass(addRemoveClass, className) {
const el = document.body;
if (addRemoveClass === 'addClass') {
el.classList.add(className);
} else {
el.classList.remove(className);
}
},
},
mounted() {
this.toggleBodyClass('addClass', 'mb-0');
},
destroyed() {
this.toggleBodyClass('removeClass', 'mb-0');
}