Reputation: 4497
I am still new to Vue and struggle with having a component update its contents as soon as connected data has been updated. The component will show the changed content as soon as it has been re-rendered though.
Component
Vue.component('report-summary', {
props: ['translation', 'store'],
watch: {
'store.state.currentModel.Definition': {
handler: function(change) {
console.log('Change detected', change);
},
deep:true
}
},
template: '<div>
'<div class="alert alert-secondary" role="alert">' +
'<h5 class="border-bottom border-dark"> {{ translation.currently_defined }}</h5>' +
'<div>{{ store.state.currentModel.Definition.length }} {{translation.elements }}</div>' +
'</div>' +
'</div>'
});
store
is passed in as a property in the HTML of the page:
<report-summary :translation="t" :store="store"></report-summary>
store
itself is a Vuex.Store:
let store = new Vuex.Store({
state: {
t: undefined,
currentModel: undefined
},
mutations: {
storeNewModel(state) {
let model = CloneFactory.sealedClone(
window.Project.Model.ReportTemplateModel);
model.Header = CloneFactory.sealedClone(
window.Project.Model.ReportTemplateHeaderModel);
state.currentModel = model;
},
storeNewModelDefinition(state, definition) {
state.currentModel.Definition.push(definition);
}
}
});
The currentModel
element gets initialized by invoking store.commit('storeNewModel');
before any data is stored in the model's Definition
attribute.
The definition's content gets updated by using store.commit('storeNewModelDefinition')
in a loop:
for (let c in this.$data.currentDefinition.charts) {
store.commit('storeNewModelDefinition', c);
}
When store.commit('storeNewModelDefinition', c);
is called the store updates as expected:
But the component won't react to the changed data:
Then when I navigate away (by changing the view which hides the embedded component) and again navigate to the view the content got updated:
In the Console window I see that the watcher hasn't been triggered at any point in time:
What am I missing here? Thanks a lot for opening my eyes in advance.
Switching the watch
to an event bus listener also didn't work as planned.
This is the initialization of the EventBus:
window.eventBus= new Vue();
I added this to my Component:
created: function() {
window.eventBus.$on('report-summary-update', function() {
this.$nextTick(function(){ this.$forceUpdate(); });
});
}
And emit the event after adding or removing elements from the list:
window.eventBus.$emit('report-summary-update');
When setting a breakpoint in the Debugger watching the this.$forceUpdate();
I see that it's also being called but the UI still will show the old content without any changes. I am really lost right now.
P.S. I cross-posted this on the Vue forum but as the community is rather small hope to receive some feedback here.
Upvotes: 8
Views: 16429
Reputation: 374
From vuejs documentation Reactivity in depth section
a property must be present in the data object in order for Vue to convert it and make it reactive
and this is one-off vue reactivity caveat. Vue cannot detect property addition or deletion.
in your state instead of currentModel: undefined
add all properties you want to be reactive like
currentModel: { definitions:[] }
then any addition or removal of an element from definitions
array would automatically become reactive.
And you can find more explanation on the documentation
Upvotes: 0
Reputation: 2139
Pushing data to a nested array always causes such issues where the component does not update.
Try adding a getter to your store and use that getter to get data from the store
let store = new Vuex.Store({
state: {
t: undefined,
currentModel: undefined
},
mutations: {
storeNewModel(state) {
let model = CloneFactory.sealedClone(
window.Project.Model.ReportTemplateModel);
model.Header = CloneFactory.sealedClone(
window.Project.Model.ReportTemplateHeaderModel);
state.currentModel = model;
},
storeNewModelDefinition(state, definition) {
state.currentModel.Definition.push(definition);
}
},
getters:{
definitions(state){
return state.currentModel.Definition
}
}
});
In your component, you can add a computed prop that gets the data from store
Vue.component('report-summary', {
props: ['translation', 'store'],
computed: {
definitions(){
return store.getters.definitions
}
},
template: '<div>
'<div class="alert alert-secondary" role="alert">' +
'<h5 class="border-bottom border-dark"> {{ translation.currently_defined }}</h5>' +
'<div>{{ definitions.length }} {{translation.elements }}</div>' +
'</div>' +
'</div>'
});
UPDATE (30 Nov, 2019)
If the above code still does not work, change your storeNewModelDefinition
mutation to this:
storeNewModelDefinition(state, definition) {
// Create a new copy of definitions
let definitions = [...state.currentModel.Definition]
// Push the new item
definitions.push(definition)
// Use `Vue.set` so that Vuejs reacts to the change
Vue.set(state.currentModel, 'Definition', definitions);
}
Upvotes: 2
Reputation: 37793
Just a note on your EventBus experiment (you should definitely try to fix the reactivity issue instead of trying tricks like $forceUpdate
)
Reason why your experiment with EventBus didn't work is the use of anonymous function as an event handler. Take a look at this piece of code:
methods: {
onEvent() {
console.log(this)
}
},
mounted() {
// this.onEvent is method where "this" is bound to current Vue instance
this.$bus.$on("test", this.onEvent);
// Handler is anonymous function - "this" refers to EventBus Vue instance
this.$bus.$on("test", function() {
console.log(this)
});
}
this
is is explicitly (by Vue itself) bound to Vue instance where the handler "lives"this
is not explicitly bound so it ends up like this == event bus Vue instance
. You can work around that using closure like this: mounted() {
let self = this;
this.$bus.$on("test", function() {
// self = "this" in context of function "mounted"
console.log(self)
});
}
this
in JS can be tricky...
Upvotes: 2