For my application, I'm mutating the object in my data: [{...}, {...}, {...}]
to state changes occurring at other open instances of my application (happening through web sockets etc, etc). I have a handler on the data structure like this below:
watch: {
todos: {
deep: true,
handler: todoStorage.save,
},
},
Triggering todoStorage.save
would unnecessarily save the contents of the array back to my database where I already know the current state of the application.
Is there a way to mutate the array _without_ triggering the handler? It seems that trying to undefine
the handler while making the operation doesn't work.
The point of a watcher is that it will fire when the data changes. Instead of thinking about stopping it from firing, just do a conditional check inside the watcher callback.
I wish I could @yyx990803 :cry:
In this case I cannot do a conditional check in the watcher callback since there's no way for me to check whether I'm going to receive more deletion messages via WebSocket which come one at a time. If every object in my data
Array is saved after every modification to the Array I get into this case:
data
data
to DBdata
which was just saved in previous step. This results in errors on my backend because I'm trying to update data that no longer exists there. Is there a recommended Vue.js pattern for this?
I don't think I understand your use case, but maybe using a deep watcher to persist the whole is just the wrong idea to begin with. You should probably use a component for each item in the array so you can do fine-grained persistence.
Hello, i guess i understand the issue from @dalanmiller.
In fact, i was stuck on a similar issue : the fact that you don't want the watcher trigger the callback every time when you manually decide to affect the value somewhere.
For instance : i got my data from an rest api call in an async way. Then i decide to affect the data in my component, but the watcher detect change and will trigger the callback that notice update the changes to my rest api : there were no data change but i got an api 'Set' call ! 😕
More into this issue, i did a codepen to explain that (VueJS 2.0) : http://codepen.io/matyo91/pen/ZBpjVz
📝 What i want is to fire change only when i manually change the value into the textarea.
Here, the 'code' variable will call onUpdateCode on sync change every time => not what i want 🐛 . And the 'coder' variable will not call onUpdateCoder because i did add a dirty silence mechanism. And onUpdateCoder is only called when i do change text into the coder textarea.
The coder is what i want 🍎 (i tried a better solution with Vue.nextTick, but it fail, i think it could have worked if Vue.nextTick can dispatch by setting a priority parameter)
I come to this code, because somehow, i got a similar issue when integrating ace.js into a VueJS component. And the ace team got the same issue and it's resolved by that comment from @nightwing : https://github.com/ajaxorg/ace/issues/503#issuecomment-163761023
The next comment from @davidthornton notice it's a https://en.wikipedia.org/wiki/Operational_transformation logic.
@yyx990803, you did already answered this in this issue : https://github.com/vuejs/vue/issues/1157
But it's not really this case here.
So i think it's more a design pattern issue. But now i don't know how to write it into a "smart code".
Any idea ?!
It seems I encounter a similar issue, my solution is that use this.$watch instead of watch option, this.$watch return a handler to unwatch
function, which I can call it when I want to stop watch, after that I watch the value again, it's terrible........
Had the same issue. But I think the fix is simple. Just use the watch function to decide if you would like to call a method:
// vue 1.0
this.$watch('myModel', function(newVal, oldVal) {
if([your statement]) {
this.loadSomeData();
}
});
I found that action of the firefox and chrome is different, in updated(), if the event was triggered, firefox fall into stuck and never come out, but chrome can deal with this correctly.
whether you use $watch and unwatch, or set a flag to prevent the watch from handling, you'll need to wait for this.$nextTick() otherwise you will unwatch/turn off the flag prematurely and the watcher will still get executed.
It should be pretty obvious at this point that this would be a rather useful feature in many situations.
Ugly:
resetForm() {
this.workbench.isResetting = true
value.tier = value.original_tier
this.nextTick(()=>{
this.workbench.isResetting = false
})
}
watch: {
'value.tier'(new_,old) {
if (this.workbench.isResetting)
return;
//...
}
}
Nice:
resetForm() {
Vue.set(value,'tier',value.original_tier, false)
}
Nice >> Ugly
I'd rather have some kind of source
added to the watch. Something along the lines of
<input type="checkbox" id="checkbox" v-source="my-html" v-model="light_is_on">
<label for="checkbox">Kitchen light is: {{ light_is_on ? "on" : "off" }}</label>
watch: {
light_is_on: function(value, old, source) {
if (source !== this.websocketHandler) {
websocket.send({'light_is_on': value})
}
if (source == 'my-html') {
console.log('watch triggered from HTML element')
}
},
},
methods: {
websocketHandler(event) {
if (event.light_is_on !== undefined) {
this.light_is_on = event.light_is_on;
}
},
},
I like the idea of getting the source from the watch function(value, old, source). I have the same problem and there are only ugly solutions.
Hello, here is what i'm doing for that problem. I had to face with reseting twice search and filters, without trigger multiple watch at the same time.
data: {
search: null,
filters: { filter1: null, filter2: null },
watchInPause: false,
},
watch: {
search() {
if (!this.watchInPause) {
this.refresh();
},
},
filters() {
handler() {
if (!this.watchInPause) {
this.refresh();
}
},
deep: true,
},
},
async onResetFilters() {
this.watchInPause = true;
this.search = null;
this.filters = {
filter1: null,
filter2: null,
};
await this.refresh();
this.watchInPause = false;
},
Hope this helps ...
I made a mixin which brings a $withoutWatchers()
method. This method disables the watchers within its callback :
$withoutWatchers(() => {
// Watchers will not get fired
})
The following is a real world example of a user's form. We want this component to take the user as a prop, copy it in an internal value, dispatch the changes when this internal value is updated, and update the internal value when the prop changes :
export default {
data: () => ({
model: null
}),
props: {
user: {
type: Object,
required: true
}
},
created () {
this.$watch('user', this.sync, {
immediate: true
})
},
methods: {
sync () {
this.$withoutWatchers(() => {
this.model = { ...this.user }
})
}
},
watch: {
model () {
this.$emit('update:user', this.model)
}
}
}
Here is the mixin
const WithoutWatchers = {
methods: {
$withoutWatchers (cb) {
const watchers = this._watchers.map((watcher) => ({ cb: watcher.cb, sync: watcher.sync }))
for (let index in this._watchers) {
this._watchers[index] = Object.assign(this._watchers[index], { cb: () => null, sync: true })
}
cb()
for (let index in this._watchers) {
this._watchers[index] = Object.assign(this._watchers[index], watchers[index])
}
}
}
}
Be aware that this mixin uses the internal Vue's API, which may change between minor versions. That would be cool to have it within Vue's core. Could we consider bringing it @yyx990803 ?
@abellion wonderful solution to this problem.
Nice work @abellion would love to get this added to vue
@abellion really nice!
active: false
instead of cb: () => null
this._watchers[index] = Object.assign(this._watchers[index], { active: false, sync: true })
or
ww[index].active = false;
ww[index].sync = true
@abellion really nice!
- What about using
active: false
instead ofcb: () => null
- Can you comment on what is more performant?
this._watchers[index] = Object.assign(this._watchers[index], { active: false, sync: true })
or
ww[index].active = false; ww[index].sync = true
I had tried to use active
and it seemed to work - but later failed, maybe, something was not updated. So I went back to using @abellion 's original version.
I'd be happy to understand, what the difference is and why sync = true
is needed.
Most helpful comment
It should be pretty obvious at this point that this would be a rather useful feature in many situations.
Ugly:
Nice:
Nice >> Ugly