Home > database >  Vue.js - watch particular properties of the object and load data on change
Vue.js - watch particular properties of the object and load data on change

Time:11-16

I have Vue component with prop named product, it is an object with a bunch of properties. And it changes often.

export default {
    props: {
        product: {
            type: Object,
            default: () => {},
        },
    },
    watch: {
        'product.p1'() {
            this.loadData()
        },
        'product.p2'() {
            this.loadData()
        },
    },
    methods: {
        loadData() {
            doApiRequest(this.product.p1, this.product.p2)
        }
    },
}

Component should load new data when only properties p1 and p2 of product are changed. The one approach is to watch the whole product and load data when it is changed. But it produces unnecessary requests because p1 and p2 may not have changed.

Another idea is to watch product.p1 and product.p2, and call same function to load data in each watcher. But it may happen that both p1 and p2 changed in new version of product, it would trigger 2 calls.

Will it be a good solution to use debounced function for data load?

Or rather use single watcher for the whole product and compare new p1 and p2 stringified with their old stringified versions to determine if data loading should be triggered?

CodePudding user response:

There are several approaches to this, each with pros and cons.

One simple approach I do is to use a watch function that accesses each of the properties you want to watch and then returns a new empty object. Vue knows product.p1 and product.p2 were accessed in the watch function, so it will re-execute it any time either of those properties change. Then, by returning a new empty object instance from the watch function, Vue will trigger the watch handler because the watch function returned a new value (and thus what is being watched "changed").

created() {
  this.$watch(() => {
    // Touch the properties we want to watch
    this.product.p1;
    this.product.p2;

    // Return a new value so Vue calls the handler when
    // this function is re-executed
    return {};
  }, () => {
    // p1 or p2 changed
  })
}

Pros:

  • You don't have to stringify anything.
  • You don't have to debounce the watch handler function.

Cons:

  • You can't track the previous values of p1 and p2.
  • Take care if this.product could ever be null/undefined.
  • It will always trigger when p1 or p2 are changed; even if p1 and p2 are set back to their previous values before the next micro task (i.e. $nextTick()); but this is unlikely to be a problem in most cases.
  • You need to use this.$watch(). If you want to use the watch option instead then you need to watch a computed property.

Some of these cons apply to other approaches anyway.

A more compact version would be:

this.$watch(
  () => (this.product.p1, this.product.p2, {}),
  () => {
    // changed
  }
})

CodePudding user response:

As some of other developers said you can use computed properties to monitor the changing of product.p1 or product.p2 or both of them and then calling loadData() method only once in each case. Here is the code of a hypothetical product.vue component:

<template>
    <div>
        this is product compo
    </div>
</template>

<script>
export default {
    name: "product",
    watch: {
        p1p2: function(newVal, oldVal) {
            this.loadData();
        }
    },
    props: {
        productProp: {
            type: Object,
            default: () => {},
        },
    },

    computed: {
        p1p2: function() {
            return this.productProp.p1   this.productProp.p2;
        }
    },
    methods: {
        loadData() {
            console.log("load data method");
        }
    },
}
</script>
<iframe name="sif1" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

I renamed the prop that it received to productProp and watched for a computed property called p1p2 in that. I supposed that the values of data are in String format (but if they are not you could convert them). Actually p1p2 is the concatenation of productProp.p1 and productProp.p2. So changing one or both of them could fire the loadData() method. Here is the code of a parent component that passes data to product.vue:

<template>
<section>
   
  <product :productProp = "dataObj"></product>
  <div class="d-flex justify-space-between mt-4">
    <v-btn @click="changeP1()">change p1</v-btn>
    <v-btn @click="changeP2()">change p2</v-btn>
    <v-btn @click="changeBoth()">change both</v-btn>
    <v-btn @click="changeOthers()">change others</v-btn>
  </div>
  
</section>
</template>

<script>
import product from "../components/product";

export default {
    name: 'parentCompo',

    data () {
      return {
        dataObj: {
          p1: "name1",
          p2: "name2",
          p3: "name3",
          p4: "name4"
        }
      }
    },
    components: {
      product
    },

    methods: {
      changeP1: function() {
        if (this.dataObj.p1 == "name1") {
          this.dataObj.p1 = "product1"
        } else {
          this.dataObj.p1 = "name1"
        }
      },

      changeP2: function() {
        if (this.dataObj.p2 == "name2") {
          this.dataObj.p2 = "product2"
        } else {
          this.dataObj.p2 = "name2"
        }
      },

      changeBoth: function() {
        if (this.dataObj.p2 == "name2") {
          this.dataObj.p2 = "product2"
        } else {
          this.dataObj.p2 = "name2"
        }

        if (this.dataObj.p1 == "name1") {
          this.dataObj.p1 = "product1"
        } else {
          this.dataObj.p1 = "name1"
        }
      },

      changeOthers: function() {
        if (this.dataObj.p3 == "name3") {
          this.dataObj.p3 = "product3"
        } else {
          this.dataObj.p3 = "name3"
        }
      }
    },
   
}
</script>
<iframe name="sif2" sandbox="allow-forms allow-modals allow-scripts" frameborder="0"></iframe>

You can test the change buttons to see that by changing dataObj.p1 or dataObj.p2 or both of them the loadData() method only called once and by changing others it is not called.

  • Related