Vue JS Watching deep nested object

55,715

Solution 1

You could use the 'watch' method.. for example if your data is:

data: {
    block: {
        checkbox: {
            active:false
        },
        someotherprop: {
            changeme: 0
        }
    }
}

You could do something like this:

data: {...},
watch: {
   'block.checkbox.active': function() {
        // checkbox active state has changed
        this.block.someotherprop.changeme = 5;
    } 
}

Solution 2

If you want to watch the object as a whole with all its properties, and not only just one property, you can do this instead:

 data() {
    return {
       object: {
          prop1: "a",
          prop2: "b",
       }    
    }
 },
 watch: {
    object: {
        handler(newVal, oldVal) {
            // do something with the object
        },
        deep: true,
    },
},

notice handler and deep: true

If you only want to watch prop1 you can do:

watch: { 
    'object.prop1' : function(newVal, oldVal) { 
        // do something here 
     }
}

Solution 3

Other solution not mentioned here: Use the deep option.

watch:{
  block: {
    handler: function () {console.log("changed") },
    deep: true
  }
}

Solution 4

Since nobody replied and I have solved/ worked around the issue by now, I thought it migth be useful to post my solution. Please note that I am not sure my solution is how these types of things should be tackled, it works though.

Instead of using this event listener v-on="change: checkboxChange(this)" I am now using a custom directive which listens to both the selected and disabled model attribute, like this: v-on-filter-change="selected, disabled".

The directive looks like this:

directives: {
    'on-filter-change': function(newVal, oldVal) {
        // When the input elements are first rendered, the on-filter-change directive is called as well, 
        // but I only want stuff to happen when a user does someting, so I return when there is no valid old value
        if (typeof oldVal === 'undefined') {
            return false;
        }
        // Do stuff here
        // this.vm is a handy attribute that contains some vue instance information as well as the current object
        // this.expression is another useful attribute with which you can assess which event has taken place
    }
},

The if clause seems a bit hacky, but I couldn't find another way. At least it all works.

Perhaps this will be useful to someone in the future.

Share:
55,715
Hendrik
Author by

Hendrik

Updated on July 09, 2022

Comments

  • Hendrik
    Hendrik almost 2 years

    Disclaimer: This is my first attempt at building an MVVM app I have also not worked with vue.js before, so it could well be that my issue is a result of a more fundamental problem.


    In my view I have two types of blocks with checkboxes:

    • Type 1: block/checkboxes
    • Type 2: block/headers/checkboxes

    The underlying object is structured like this:

    {
      "someTopLevelSetting": "someValue",
      "blocks": [
        {
          "name": "someBlockName",
          "categryLevel": "false",
          "variables": [
            {
              "name": "someVarName",
              "value": "someVarValue",
              "selected": false,
              "disabled": false
            }
          ]
        },
        {
          "name": "someOtherBlockName",
          "categryLevel": "true",
          "variables": [
            {
              "name": "someVarName",
              "value": "someVarValue",
              "categories": [
                {
                  "name": "SomeCatName",
                  "value": "someCatValue",
                  "selected": false,
                  "disabled": false
                }
              ]
            }
          ]
        }
      ]
    }
    

    My objectives

    Selecting checkboxes:

    1. User clicks on checkbox, checkbox is selected (selected=true)
    2. A method is fired to check if any other checkboxes need to be disabled (disabled=true). (If this method has indeed disabled anything, it also calls itself again, because other items could be in turn dependent on the disabled item)
    3. Another method updates some other things, like icons etc

    Clearing checkboxes

    A user can click on a "clear" button, which unchecks all checkboxes in a list (selected=false). This action should also trigger the methods that optionally disables checkboxes and updates icons etc.

    My current method (which doesn't seem quite right)

    • The selected attribute of the data-model is bound to the checked state of the checkbox element via the v-model directive.
    • The disabled attribute (from the model) is bound to the element's class and disabled attribute. This state is set by the aforementioned method.
    • To initialize the methods that disable checkboxes and change some icons, I am using a v-on="change: checkboxChange(this)" directive. I think I need to do this part differently
    • The clearList method is called via v-on="click: clearList(this)"

    The problems with my current setup is that the change event is not firing when the checkboxes are cleared programatically (i.e. not by user interaction).

    What I would like instead
    To me the most logical thing to do would be to use this.$watch and keep track of changes in the model, instead of listening for DOM events.

    Once there is a change I would then need to identify which exact item changed, and act on that. I have tried to create a $watch function that observes the blocks array. This seems to pick up on the changes fine, but it is returning the full object, as opposed to the individual attribute that has changed. Also this object lacks some convenient helper attributes, like $parent.

    I can think of some hacky ways to make the app work (like manually firing change events in my clearList method, etc.) but my use case seems pretty standard, so I expect there is probably a much more elegant way to handle this.

  • Hendrik
    Hendrik over 8 years
    .selected and .disabled are properties of anonymous variable objects which belong to the variables array. Your example works because it's just a single object not an array.
  • Apit John Ismail
    Apit John Ismail about 7 years
    I did something like block.checkbox.active, but it does not work until I add them in " " like you mention above. Thanks. It works now.
  • Hendrik
    Hendrik over 6 years
    Thanks for this. However, since my question is over two years old, and I haven't used Vue since then, I can't verify whether this works.
  • peerbolte
    peerbolte over 6 years
    @Hendrik no worries, I hope this helps someone who has the same issue
  • Sylvain Attoumani
    Sylvain Attoumani over 6 years
    Do you know what I should do if i only want to watch prop1 ?
  • peerbolte
    peerbolte over 6 years
    @SylvainAttoumani yes: in case of my example: watch: { 'object.prop1' : function(newVal, oldVal) { // do something here }}
  • TKharaishvili
    TKharaishvili about 3 years
    @peerbolte I think that comment should be part of the answer. You'd get way more upvotes.