0

I have an array items of objects:

  data() {
    return {
      items: [
          {
              id: 101,
              firstName: 'Mohammed',
              lastName: 'Chang',
              dob: '1995-03-21'
          },
          {
              id: 102,
              firstName: 'John',
              lastName: 'Smith',
              dob: '1989-10-11'
          },
          {
              id: 103,
              firstName: 'Lulu',
              lastName: 'Lemon',
              dob: '1945-01-29'
          },
      ]
    }
  },

If I use a regular watch, and try to compare the items, because the items are objects, they will reference the same thing, so they will always return as unchanged:

 watch: {
    items: {
        handler: function(revised, original) {
            for (let i = 0; i < revised.length; i++) {
                // check each item in revised to see if it's changed from original
                if (this.isChanged(original, revised[i])) {
                    axios.put(URL + '/' + revised[i].id, revised[i]);
                }
            }
        },
        deep: true
    }
  },
  methods: {
      isChanged(original, item) {
        // this function won't work because item is an object
        // which will be referenced in original, so it will always
        // be a match, i.e. not changed
        for (let i = 0; i < revised.length; i++) {
          // found a matching ID in the original
          if (original[i].id === revised.id) {
              // compare the item against the original and return
              // true for changed and false for not changed
              return JSON.stringify(original[i]) !== JSON.stringify(item);
          }
          // no match? assume true
          return true;
        }
      }
    }
  }

If I use a watch like this, by referencing each item directly, then this will work:

 watch: {
    'items.0': {
        handler: function(revised) {
            axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    },
    'items.1': {
        handler: function(revised) {
            axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    },
    'items.2': {
        handler: function(revised) {
             axios.put(URL + '/' + revised.id, revised);
        },
        deep: true
    }
  },

However this approach is not very practical. What if I dynamically add an item to the array? It won't have a watcher.

Is there a way to do a watch like above items.# but using wildcards or regular expressions?

Because there's no way to compare the original object, do most developers just update each element in the entire array with its own PUT call? This seems wasteful, especially if dealing with many very large objects.

 watch: {
    items: {
        handler: function(revised) {
            // no way to know which item is changed, so just
            // update each item - wasteful, I know
            for (let i = 0; i < revised.length; i++) {
                axios.put(URL + '/' + revised[i].id, revised[i]);
            }
        },
        deep: true
    }
  },
4
  • Why do you need to watch the entire collection? Also, you're missing deep:true after handler: {}... There's probably a better way to do this, but it's hard to determine how you're updating them so we can advise. Can you include that? Commented Dec 19, 2018 at 18:25
  • you need to provide more info regarding what you're trying to do exactly. Commented Dec 19, 2018 at 18:38
  • @Ohgodwhy - the user has access to the entire collection and can change any element. e.g. imagine a grid interface. Using deep: true still exhibits the same functionality. Have added put statements. Commented Dec 19, 2018 at 19:31
  • @Dabbas - have added put statements. Commented Dec 19, 2018 at 19:32

1 Answer 1

1

If I use a regular watch, and try to compare the items, because the items are objects, they will reference the same thing, so they will always return as unchanged:

Yes, in cases like this you have not available the previous value in the handler, only new one.

If I use a watch like this, by referencing each item directly, then this will work:

Yes, it will. Because you are not watching nested objects anymore.

However this approach is not very practical. What if I dynamically add an item to the array? It won't have a watcher.

Right, it will not have a watcher.

Is there a way to do a watch like above items.# but using wildcards or regular expressions?

No, there is nothing like that.

Because there's no way to compare the original object, do most developers just update each element in the entire array with its own PUT call? This seems wasteful, especially if dealing with many very large objects.

I'm lost... Why should anyone update every element in entire array? What do you mean by "own PUT call"? Can you edit your question and give me some code example with that PUT call? Can you show me what you're trying to do? To achieve up?

EDIT

Now I understand... First: every application should have only one point of truth. And in your case it is the external database, not the SPA itself. SPA just visualize that data. So, core of your problem can be solved (not only) that way:

Grab the data from source of truth, from backend. For example during the app creation:

new Vue({
  el: '#app',

  data: {
    items: []
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })
  }
}

Then do not change them directly. For example, like this:

new Vue({ el: '#app',

  data: {
    items: []
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })

    // Bad
    setTimeout(() => {
      this.items[1].firstName = 'Jan'
    }, 2000)
  }
}

But this way:

new Vue({ el: '#app',

  data: {
    items: []
  },

  methods: {
    updateItems (idx, obj) {
      this.item[idx] = { ...this.items[idx], ...obj }
    }
  },

  created () {
    this.$axios
      .get('mountpoint')
      .then(({ data }) => {
        this.items = data
      })

    // Good, through some kind of setter
    setTimeout(() => {
      updateItems(1, { firstName': 'Jan' })
    }, 2000)
  }
}

What is it good for? Now, in that setter you can start to test new value if it is different like existing one, before you make any change. And not only. If you detect some changes, still do not mutate items array. Post this change to the backend with axios first, and change items only when backend respond the change was successful.

Sign up to request clarification or add additional context in comments.

2 Comments

Added PUT statement and also code to show updating each item with its own PUT statement.
@Buddy So, with that PUT you mean updating external structure with async call?

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.