vue js watch multiple properties with single handler
Asked Answered
S

13

129

Currently I have to watch a few properties. And if each of them changes, I have to invoke the same function:

export default{
  // ...... rest of code 
  watch: {
    propa: function(after,before) {
      doSomething(after,before);
    },
    propb: function(after,before) {
      doSomething(after,before);
    }
    // ... so on
  }
}

So I am having to write the same code multiple times above. Is it possible to simply have all properties being watched and invoke their change handler without having to write same code multiple times?

PS: I am using vue 1.x

Stat answered 11/3, 2017 at 15:33 Comment(2)
Depends on how your data are structured.If you put watched data in one object you can watch that single object with deep: true property, and triger that method.Also you can watch whole data object but I don't suggest to do that.Juno
I dont think there is some way as discussed here, You can create a computed property as done here but that also is not very clean.Shirberg
O
212

Update: April-2020

For people who are using Vue 3, the watch API can accept multiple sources

import { watch, ref } from 'vue';

export default {
  setup(() => {
    const a = ref(1), b = ref('hello');

    watch([a, b], ([newA, newB], [prevA, prevB]) => {
      // do whatever you want
    });
  });
};


Original answer for Vue 2

there is no official way to solve your question(see this). but you can use the computed property as a trick:

    export default {
      // ...
      computed: {
        propertyAAndPropertyB() {
          return `${this.propertyA}|${this.propertyB}`;
        },
      },
      watch: {
        propertyAAndPropertyB(newVal, oldVal) {
          const [oldPropertyA, oldProvertyB] = oldVal.split('|');
          const [newPropertyA, newProvertyB] = newVal.split('|');
          // doSomething
        },
      },
    }

if you just want to do something and don't care about what's new/old values. ignore two lines

    const [oldPropertyA, oldProvertyB] = oldVal.split('|');
    const [newPropertyA, newProvertyB] = newVal.split('|');
Onym answered 24/8, 2017 at 5:8 Comment(5)
As of your April update, I am interesting in using this approach, but am unable to find the documentation as I am unable to get the syntax just right, only found the docs of v2 and of v3, but these both do not show the syntax I need. Need the syntax for: export default { watch:{ --watcher for multiple props here-- } }Roye
Using vue 2 and composition-api plugin, It works with a function returning an array of values to watch: watch(() => [a, b], ....)Stope
Using vue 2, I get [Object object] when I console any of the properties I get in the watch method. I guess it's not working?Alcus
@Alcus The answer coerces it into a string. If you need to have any other types, best would be to return an object from the computed property. startAndEnd() { const {start, end} = this; return { start, end }; },. You can then use the different values in their corect type with newVal.start and newVal.end for example. You must make sure to emit a newly created Object each time so you don't get mutated objects in your oldVal.Bazluke
I'm using 3.0. I have a long list of variables that I want to watch, but I don't care about the values of them. How can I write my watch statement and not include the variable values? I haven't had any luck coming up with a syntax and there doesn't seem to be any documentation on this.Eskisehir
A
37

Another possibility:

new Vue({
  el: '#app',
  data: {
    name: 'Alice',
    surname: 'Smith',
    fullName: '' // IRL you would use a computed for this, I'm updating it using a watch just to demo how it'd be used
  },
  mounted() {
    this.$watch(vm => [vm.name, vm.surname], val => {
      
      this.fullName = this.name + ' ' + this.surname;
      
    }, {
      immediate: true, // run immediately
      deep: true // detects changes inside objects. not needed here, but maybe in other cases
    }) 
  }
});
<script src="https://unpkg.com/vue"></script>

<div id="app">
  <div>
    name:<input v-model="name">
  </div>
  <div>
    surname:<input v-model="surname">
  </div>
  <div>
    full name: {{ fullName }}
  </div>
</div>

More info on the Vue API docs for vm.$watch.

Acetify answered 16/4, 2018 at 23:16 Comment(4)
This was super helpful to me, however it should be noted that I was watching an array of objects and for that I needed to add {deep: true} The official Vue JS Docs are actually quite helpful on this subject: vuejs.org/v2/api/#vm-watchMelodize
@JohnMellor alright, good to know. I added the link and the deep: true in the example above, just in caseAcetify
This is super cool, thanks for sharing!!Pianism
Using TypeScript, I get an error saying "no overload matches this call". Any ideas how to solve that please?Endostosis
G
30

like this:

data() {
  return {
    propa: '',
    propb: ''
  }
},
computed: {
  changeData() {
    const { propa, propb } = this
    return {
      propa,
      propb
    }
  }
},
watch: {
  changeData: {
    handler: function(val) {
      console.log('value change: ', val)
    },
    deep: true
  }
}
Gonyea answered 10/9, 2019 at 4:40 Comment(3)
You can simply return an array of items to track. changeData() { return [this.propa, this.propb] }Gabbi
This is the best answer. Retains type and is simple to implement without this.$watch. Array would work as well, but by returning an object you keep the names of each property intact, instead of having to rely on indices.Bazluke
This should be marked as the correct answer for vue2.Backfill
G
9

First, your definition could be simplified. doSomething does not appear to be a method on the Vue, so your watch could just be

watch:{
    propa: doSomething,
    propb: doSomething
}

Second, sometimes it's important to remember Vue definition objects are just plain javascript objects. They can be manipulated.

If you wanted to watch every property in your data object, you could do something like this

function doSomething(after, before){
  console.log(after,before);
}

function buildWatch(def){
  if (!def.watch)
    def.watch = {};
  for (let prop of Object.keys(def.data))
    def.watch[prop] = doSomething;
  return def;
}

let vueDefinition = {
  data:{
    propa: "testing",
    propb: "testing2",
    propc: "testing3"
  }
}

export default buildWatch(vueDefinition)

If you wanted to watch only some defined list of your properties:

// First argument is the definition, the rest are property names
function buildWatch(def){
  if (!def.watch)
    def.watch = {};
  const properties = Array.prototype.slice.call(arguments,1); 
  for (let prop of properties)
    def.watch[prop] = doSomething;
  return def;
}

export default buildWatch(vueDefinition, "propa", "propb")
Galsworthy answered 11/3, 2017 at 18:24 Comment(0)
A
5

For Vue typescript you can do like this. Tested.

  @Watch('varA')
  @Watch('varB')
  private oneOfAboveChanged(newVal) {
    console.log(newVal)
  }
Armored answered 23/9, 2020 at 14:34 Comment(0)
N
5

My resolution for vue2:

export default {
  data() {
    return {
      status: null,
      name: null,
      date: null,
      mobile: null,
      page: 1,
    }
  },
  watch: {
    ...["status", "name", "date", "mobile", "page"].reduce((acc, currentKey) => {
      acc[currentKey] = (newValue) => {
        // doSomething
        // console.log(newValue, currentKey)
      }
      return acc
    }, {}),
  }
}
Neelon answered 3/6, 2021 at 8:29 Comment(0)
I
4

vm.$data

If you want to listen to all the properties inside data(), you can use this.$data

<script>
export default {
  data () {
    return {
      propA: 'Hello',
      propB: 'world'
    }
  }
  watch: {
    $data (newValue) { // Watches for any changes in data()
      // Do something with the new data
    }    
  }
}
</script>
Ineducable answered 3/7, 2021 at 11:13 Comment(0)
M
1

Old question but the answer still may be useful for those who are still working in vue 1.

You can watch multiple props by wrapping them in quotes:

data() {
   return {
      foo: {
         prop1: 1,
         prop2: 2,
      }
   }
}
watch: {
    '[foo.prop1, foo.prop2]'(newVal, oldVal) {
        //do sth
        console.log(newVal); // prints ([prop1, prop2])
        console.log(oldVal); // prints ([prop1, prop2])
    }
}
Mittel answered 9/9, 2021 at 8:32 Comment(0)
G
0

If someone is looking for this in 2023 and is using Vue3, another possibility and a better option is to just use watchEffect.

https://vuejs.org/api/reactivity-core.html#watcheffect

Genovese answered 31/7, 2023 at 7:52 Comment(0)
S
0

This currently works perfectly in vue3 vuetify. Nothing above worked for me.

data() 
  {
    return {
      prop1: 75000,
      prop2: 12000,
      prop3: 8000,
      prop4: 30000,
      prop5: 5000,
      prop6: 8000,
      prop7: 12000,
}
};
computed: {
    combined() 
    {
      return `${this.prop1}
             |${this.prop2}
             |${this.prop3}
             |${this.prop4}
             |${this.prop5}
             |${this.prop6}
             |${this.prop7}
             `; 
    },
  },
watch: 
  { 
    combined(newVal, oldVal) 
    {
      const newValues = newVal.split('|').map(Number);
      const newValuesum = newValues.reduce
                          ((acc: any, item: any) => acc + item, 0);
    },
  }
Slashing answered 18/12, 2023 at 2:43 Comment(0)
C
0

Old question but for those who are using the Vue composition API the answer is to use the array syntax in your watcher:

watch([fooRef, barRef]) => {
  doSomething();
})

The API will also give you old and new values in an array:

watch([fooRef, barRef], ([foo, bar], [prevFoo, prevBar]) => {
  doSomething();
})

Docs

Cloy answered 30/1 at 18:11 Comment(0)
T
0

Vue provides a Deep Watcher. You can implement it like so:

watch(() => {
  return {
    firstSource: someRef.value,
    secondSource: anotherRef.value
  }
}, (newVal, oldVal) => {
  //  this watcher will be triggered on change of any of two provided sources
  // newVal = { firstSource: newData, secondSource: newData }
  handler()
})

This might be handy for your case.
You can find it in the docs for further reading

Tanbark answered 23/4 at 8:18 Comment(1)
please state the sources of your informationFew
S
-1

Below is very basic example of watch for Nuxt 3

<script setup>
const state = reactive({
  v1: "",
  v2: "",
});

// watch single value
watch(
  () => state.v1,
  (newVal, oldValue) => {
    // .....
  }
);

// watch multiple value
watch(
  () => [state.v1, state.v2],
  ([newV1, newV2], [oldV1, oldV2]) => {
    // .....
  }
);
</script>
Stlaurent answered 30/11, 2023 at 9:42 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.