Does Vue.js have a built in way to add a copy of a persistent object to a repeated array
Asked Answered
A

6

34

I have a Vue.js app where I have a v-repeat on an array of items. I want to add a newItem to the list of items. When I try this.items.push(this.newItem) the object pushed is still bound to the input. Consider the below:

new Vue({
  el: '#demo',

  data: {
    items: [
      {
        start: '12:15',
        end: '13:15',
        name: 'Whatch Vue.js Laracast',
        description: 'Watched the Laracast series on Vue.js',
        tags: ['learning', 'Vue.js', 'Laracast', 'PHP'],
        note: "Vue.js is really awesome. Thanks Evan You!!!"
      },
      {
        start: '13:15',
        end: '13:30',
        name: "Rubik's Cube",
        description: "Play with my Rubik's Cube",
        tags: ['Logic', 'Puzzle', "Rubik's Cube"],
        note: "Learned a new algorithm."
      }
    ],
    newItem: {start: '', end: '', name: '', description: '', tags: '', note: ''}
  },

  methods: {
    addItem: function(e) {
      e.preventDefault();

      this.items.push(this.newItem);
    }
  }
});

The above will, as expected, push the object that is bound onto the items array. The problem is I want just a copy of the object so it will no longer change when the input changes. See this this fiddle. I know I can do:

addItem: function(e) {
  e.preventDefault();
  this.items.push({
    name:        this.newItem.name,
    start:       this.newItem.start,
    end:         this.newItem.end,
    description: this.newItem.description,
    tags:        this.newItem.tags,
    notes:       this.newItem.notes
  })
}

This works but is a lot of repetition.

The question: Is there a built in way to add just a copy of the object instead of the persistent object.

Amuck answered 1/6, 2015 at 16:11 Comment(1)
I understand there could be a columns array in data to generate the columns and it's models etc.. Also the tags field isn't kept as an array. I copied this from a project I am starting and half implemented it for an example. Just ignore these.Amuck
A
57

See this issue on GitHub.

Shallow Clone

I was using jQuery's $.extend until Evan You pointed out there is an undocumented built it extend function Vue.util.extend that does a shallow clone. So what you could use is:

addItem: function(e) {
  e.preventDefault();

  this.items.push(Vue.util.extend({}, this.newItem));
}

See the updated Fiddle.

Deep Clone

When doing a shallow clone on an object that references other objects you copy the references to the external objects instead of cloning them. To clone the object completely do a Deep Clone.

For the deep clone, per Evan's suggestion in the first link, one could use: JSON.parse(JSON.stringify(object)). This can be seen between this fiddle and this fiddle.

If using lodash check out lodash cloneDeep. If using NPM check out clone-deep.

Amuck answered 1/6, 2015 at 16:11 Comment(4)
Evan's comment on that issue states that it is a shallow clone. Only the top level properties are cloned.Pul
i tried clone-deep and it's working fine. I just wanted to make sure I have a sort of snapshot copy of an obj that had a vue component in there and _.cloneDeep and most libs and functions don't work well, but clone-deep did it for me.Grillage
I fixed clone-deep so it handles circular references, see github.com/emahuni/clone-deep.gitGrillage
This article was a good read: code.tutsplus.com/articles/…Amuck
C
16

This didn't work for me (vue 1.0.13). I used the following to create a copy without the data bindings:

this.items.push( JSON.parse( JSON.stringify( newItem ) ) );
Chilli answered 9/1, 2016 at 10:13 Comment(1)
I meant DutGRIFF's answer: this.items.push(Vue.util.extend({}, this.newItem));Chilli
K
9

UPDATE 2024:

You should use structuredClone to clone removing the objects references:

  addItem: function(e) {
    e.preventDefault();
  
    this.items.push(structuredClone(this.newItem));
  }

UPDATE:

You can also use Object Spread:

  addItem: function(e) {
    e.preventDefault();
  
    this.items.push({...this.newItem});
  }

OLD ANSWER:

You can use Vanilla JavaScript with Object.assign():

  addItem: function(e) {
    e.preventDefault();

    this.items.push(Object.assign({}, this.newItem));
  }
Ko answered 7/5, 2018 at 12:52 Comment(2)
Object.assign creates a shallow copy. I am assuming that the object spread also creates a shallow copy, right?Edmon
Not a deep. Make this simple test: a = { nivel1: { nivel2: { nivel3: 'nivel 3' } }, } b = a b.nivel1.nivel2.nivel3 = 'new value' a.nivel1.nivel2.nivel3 // 'new value'Darill
P
4

The top answer is wrong. Vue.util.extend has nothing to do with jQuery's extend. It's always a shallow clone. https://github.com/vuejs/vue/issues/1849#issuecomment-158770874

Object.assign and Spread operator are also shallow copy. see this https://scotch.io/bar-talk/copying-objects-in-javascript

Simply use the implementation of Ramda.js https://github.com/ramda/ramda/blob/v0.26.1/source/internal/_clone.js

_curry is not necessary if you don't want them.

Or check this MVA What is the most efficient way to deep clone an object in JavaScript?

Puglia answered 1/8, 2019 at 2:50 Comment(0)
T
3

structuredClone can be used but there is a little gotcha. It cannot clone an Proxy which is the way Vue 3 implements reactivity. So you must first get the target and clone this:

import { toRaw } from 'vue'
...
const someCopy = structuredClone(toRaw(someObject))
Thieve answered 9/6, 2023 at 9:39 Comment(0)
V
0

Using lodash

import _ from 'lodash';


this.form_data = _.cloneDeep(this.form_data_initial);

this.form_data = _.merge(_.cloneDeep(this.form_data), { filter: _.cloneDeep(this.form_data_initial.filter) });
Verlaverlee answered 17/4 at 15:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.