Someone
Someone

Reputation: 3568

VueJS deep watcher - specific property on multiple objects

The Problem

I have a "products" array with multiple objects. Each product object contains the property "price". I want to watch this property in each product for possible changes. I am using this to calculate a commission price when the user changes the price in an input box.

My products array looks like this;

[
  0: {
    name: ...,
    price: ...,
    commission: ...,
  },
  1: {
    name: ...,
    price: ...,
    commission: ...,
  },
  2: {
    name: ...,
    price: ...,
    commission: ...,
  },
  ...
  ...
  ...
]

My code

I've tried this, but it doesn't detect any changes except for when the products are first loaded;

    watch  : {
        // Watch for changes in the product price, in order to calculate final price with commission
        'products.price': {
            handler: function (after, before) {
                console.log('The price changed!');
            },
            deep   : true
        }
    },

The products are loaded like this;

mounted: async function () {
            this.products = await this.apiRequest('event/1/products').then(function (products) {
                // Attach reactive properties 'delete' & 'chosen' to all products so these can be toggled in real time
                for (let product of products) {
                    console.log(product.absorb);
                    Vue.set(product, 'delete', false);
                    Vue.set(product, 'chosen', product.absorb);
                }

                console.log(products);

                return products;
            })
        }

Other questions I've looked at Vue.js watching deep properties This one is trying to watch a property that does not yet exist. VueJs watching deep changes in object This one is watching for changes in another component.

Upvotes: 4

Views: 2177

Answers (1)

Ackroydd
Ackroydd

Reputation: 1610

You can't really deep-watch products.price, because price is a property of individual product, not the products array.

Declarative watchers are problematic with arrays, if you attempt to use an index in the watch expression, e.g products[0].price, you get a warning from Vue

[Vue warn]: Failed watching path: “products[0].price”. Watcher only accepts simple dot-delimited paths. For full control, use a function instead.

What this means is you can use a programmatic watch with a function, but it's not explained all that well.

Here is one way to do it in your scenario

<script>
export default {
  name: "Products",
  data() {
    return {
      products: []
    };
  },
  mounted: async function() {
    this.products = await this.apiRequest('event/1/products')...

    console.log("After assigning to this.products", this.products);

    // Add watchers here, using a common handler
    this.products.forEach(p => this.$watch(() => p.price, this.onPriceChanged) );

    // Simulate a change
    setTimeout(() => {
      console.log("Changing price");
      this.products[0].price= 100;
    }, 1000);
  },
  methods: {
    onPriceChanged(after, before) {
      console.log(before, after);
    }
  }
};
</script>

Here is my test Codesandbox (I use color instead of price because there's no price in the test api)

Upvotes: 2

Related Questions