17

I am new to VueJs and I am working on a form that I want to enable the Save button only when a change occurs at the model.

My initial thought is to compute a dirty function comparing the initial model with the current.

Note: This code is not tested, it's here just for an example.

var app = new Vue({
    el: '#app',
    data: {a:0, b:'', c:{c1:null, c2:0, c3:'test'}},
    initialData: null,
    mounted():{ initialData = JSON.parse(JSON.stringify(data));},
    computed: {
        isDirty: function () {
          return JSON.stringify(data) === JSON.stringify(initialData) 
        }
    }
});

Is there a better way of doing this or is there any improvement you could suggest on the above-mentioned code?

Reza Mousavi
  • 4,420
  • 5
  • 31
  • 48
Menelaos Vergis
  • 3,715
  • 5
  • 30
  • 46
  • [Vuelidate](https://monterail.github.io/vuelidate/#sub-basic-form) is a convenient way to integrate dirty checking into your app. Vue doesn't offer much out-of-the-box in this regard afaik. Your method works as well but you should probably make use of **deep comparison** rather than serializing and comparing strings (_lodash_ for example has a [lodash.isEqual()](https://lodash.com/docs/4.17.10#isEqual) which is helpful for that). – FK82 Sep 15 '18 at 08:52
  • This is the approach I've followed for Angular and Vue applications without fail. It affords you unlimited freedom, I feel. As others noted, JSON stringifying/parsing can have a few limitations but even these can be overcome one at a time or through checking specific fields as/if needed. The only change I'd add is to negate the comparison in the sample code, though you did note that the code was not tested. – Draghon Jan 07 '21 at 16:57
  • @Draghon JSON.stringify has might fail if some members are missing or are having different order. It's better to use a [deepEqual](https://stackoverflow.com/a/25456134/1503307) function – Menelaos Vergis Jan 08 '21 at 17:22
  • Have you found a good solution yet without package? – Dgloria May 16 '22 at 17:30
  • 1
    @Dgloria Do you mean about the object compare? Check the answer's comments. I keep a copy from the initial data, and I use stringify for comparison (I only have primitive data types) – Menelaos Vergis May 17 '22 at 09:35
  • Hi, yes, In fact I'd like to get the difference as array and show the user what changes has been made. – Dgloria May 19 '22 at 04:51

2 Answers2

17

You can use the deep option of watch as shown in the manual

var app = new Vue({
el: '#app',
data: 
{
  model:
  {
    a:0, 
    b:'', 
    c:
    {
      c1:null, 
      c2:0, 
      c3:'test'
    }
  },
  dirty: false
},
watch:
{
  model:
  {
    handler(newVal, oldVal)
    {
      this.dirty = true;
    },
    deep: true
  }
}
});
tony19
  • 125,647
  • 18
  • 229
  • 307
IVO GELOV
  • 13,496
  • 1
  • 17
  • 26
  • 3
    Thank you for your answer but I want to test if the model differs from the initial model. If someone change a value and then change it back then the model is not dirty – Menelaos Vergis Sep 15 '18 at 16:10
  • 1
    Then you’ll need to keep a copy of the loaded model and compare it to the model updated by user in its watcher so you can update the dirty attribute... – David Heremans Sep 15 '18 at 18:42
  • 1
    If your data model contains only primitive data types (Number, String, Boolean, Null) - then you can get away with your initial thought. However, if your data model contains Dates, Regexes, Infinity, NaN, cyclic references - then JSON.stringify will choke on them and either produce wrong result or completely fail (e.g. with cyclic references). In such case you will have to use something like deValue (https://github.com/Rich-Harris/devalue) or JSON-dry (https://github.com/skerit/json-dry) – IVO GELOV Sep 16 '18 at 09:31
  • This thing is triggered each time I type something into an input field. Is there a way to do it only when unfocused ? – lbris Mar 10 '20 at 10:10
  • 1
    You can remove the watcher and attach a `@blur` or `@change` event handler instead. – IVO GELOV Mar 11 '20 at 15:29
  • 1
    This mostly worked for me, but dirty was always true, because the watch triggered every time I switched to a new record. replacing `this.dirty = true;` with `this.dirty = oldVal.id == newVal.id ? true : false;` fixed the issue. – Nat Apr 01 '20 at 17:12
  • 1
    @Nat, you would write it down easier `this.dirty = oldVal.id === newVal.id` – Yurii Holskyi Oct 26 '20 at 14:36
5

Borrowing from -- > https://stackoverflow.com/a/48579303/4050261

You can bind single onchange event on the parent container and benefit from the fact that change events bubble:

<div class="container" @change="someThingChanged()">
  <input v-model="foo">
  <input v-model="bar">
  ... etc.
</div>
Adarsh Madrecha
  • 6,364
  • 11
  • 69
  • 117
  • 2
    Thank you for your answer but I find deep watcher much more clean solution. This is because it's self contained in Vue object and not depending on Template code, any migration to Vuex would be much easier if used watch deep. – Menelaos Vergis Mar 25 '20 at 08:37
  • This only works for certain type of components, try mixing radios, switch, textarea etc and soon you'll see some of these are not tracked. – code4jhon Jun 30 '22 at 16:31