I'm using the decorator syntax which consists of putting @Watch(path) before a class method so that method is called when the property at the specified path changes. It is quite natural to put this decorator with the same path before more than one method, expecting the method to be called whenever an update occurs, but actually the last @Watch(path) decorator overwrites all the others. In order to support this, I think the handler option of the watch component option would need to allow an array of functions. (Then only a small update to the @Watch decorator definition would be required - at the moment I am using a variant of the code in the vue-property-decorator package).
The handler option of the watch component option should allow an array of functions.
Actually it would probably make more sense for watch[path] to accept an array of the currently supported types since each @Watch decorator could take different options.
When do you need to call multiple watcher functions on the same property? It's really weird to split a watcher up
I don't think it's that weird. In my case I have some radio buttons and selecting a different one requires some other parts of the view to be updated. It happened that there were two parts of the view that needed updating and I already had two separate functions to update each part of the view. As I said above, I'm using decorators, so thought I'd just add a Watch(path) decorator before both of those functions, but at the moment only the second one gets called because it overwrites the first one. It's probably possible to change the definition of the decorator so that this works without allowing an array (e.g. by replacing the handler function by another that calls the previous handler function as well as the new one), although this seems a bit hackish and it wouldn't support different watch options. If watch[path] accepted an array as I suggested in my comment it would be very easy to design the @Watch decorator to support this scenario.
Could you share the radio button example, please? Preferably in plain js 馃檪
What I was trying to do was something like this (much code omitted - this is typescript but is pretty much just ES6/7).
@Component({
template: template
})
class MyClass extends Vue {
boundingBox: {x:number, y: number, width:number, height:number};
naturalSize: {width:number, height:number};
naturalViewBox: {x:number, y: number, width:number, height:number};
vm = {
formats: [{name: 'SVG', ref: 'svg'}, {name: 'PNG', ref: 'png'}],
exportAreaTypes: [{name: 'Whole', ref: 'whole'}, {name: 'Current view', ref: 'current'}, {name: 'Specify', ref: 'specify'}],
outputSizeTypes: [{name: 'Match input', ref: 'match-input'}, {name: 'Specify', ref: 'specify'}],
options: {
format: 'svg',
embedFont: true,
exportAreaType: 'whole',
exportArea: {x:0, y:0, width: 0, height: 0},
outputSizeType: 'match-input',
outputSize: {width: 0, height: 0},
preserveAspectRatio: true
}
}
@Watch('vm.options.outputSizeType')
@Watch('vm.options.exportAreaType')
updateOutputSize(){
this.vm.options.outputSize = this.getOutputSize();
}
@Watch('vm.options.exportAreaType')
updateExportArea(){
this.vm.options.exportArea = this.getExportViewBox();
}
//...
}
I think this code is quite intuitive when you see it written down. (Please don't be distracted by the fact that I call the object containing most of my data vm as opposed to calling the Vue instance vm). However, when vm.options.exportAreaType changes, updateOutputSize is never called. This is because the decorator is implemented as follows.
import Vue from 'vue';
import { WatchOptions } from 'vue';
import VueClassComponent, { createDecorator } from 'vue-class-component';
export function Watch(path: string, options: WatchOptions = {}): MethodDecorator {
const { deep = false, immediate = false } = options
return createDecorator((componentOptions, handler) => {
if (typeof componentOptions.watch !== 'object') {
componentOptions.watch = Object.create(null)
}
(componentOptions.watch as any)[path] = { handler, deep, immediate }
})
}
So the second @Watch decorator overwrites the first. In order to support multiple @Watch decorators for the same path I think componentOptions.watch[path] should support an array. (Other workarounds are possible but seem a bit ugly).
Why not just maintain an array in your own code and provide Vue one watch handler? I don't think it is hard for either TS users or vanilla Vue users.
But in your example, computed properties seems to be a much better choice than watchers
@posva - In my example this.vm.options.outputSize.width etc can also be changed via a text box so can't be a computed property.
@HerringtonDarkholme - yes this is possible. I think main issue is that the example code I gave looks like it should work but it doesn't. This means that someone who doesn't have a detailed knowledge of Vue could write that code and then spend ages wondering why updateOutputSize isn't called when vm.options.exportAreaType changes (I spent a certain amount of time myself). Plus there is a slight overhead of having to write a dedicated function for the watcher. It would be possible to modify the @Watch decorator definition so that my example works, but I'm not sure how to handle the case where you might provide different watch options to each @Watch decorator.
You can probably use a computed setter in that case. If the point is just splitting up the methods, you should watch in the created hook:
this.$watch('key', () => {
this.updateOne()
this.updateTwo()
})
You can also do something similar with your decorator btw
Ok, maybe I will see if I can call $watch in my decorator definition, but I need to learn more about decorators first. For the time being having a dedicated method for each @Watch(key) is not too much extra effort. But I still think it would be quite easy to add support for the component option watch[key] to be an array, and this would also cover the case where someone might want to have two watchers with different options (e.g. deep = false or true) for the same key.
How about writing like this below? There's no need to fix or rewrite @Watch function.
@Component
class MyClass extends Vue {
@Watch('vm.options.exportAreaType')
exportAreaTypeChanged() {
this.updateOutputSize()
this.updateExportArea()
}
@Watch('vm.options.outputSizeType')
outputSizeTypeChanged() {
this.updateOutputSize()
}
updateOutputSize(){
this.vm.options.outputSize = this.getOutputSize();
}
updateExportArea(){
this.vm.options.exportArea = this.getExportViewBox();
}
}
Since a valid alterantive strategy seems to have been proposed, I will close this as the new feature doesn't seem to offer a tangible benefit.
Most helpful comment
How about writing like this below? There's no need to fix or rewrite
@Watchfunction.