Cloning an array in Javascript / Typescript

I have an array of two objects:

genericItems: Item[] = [];
backupData: Item[] = [];

      

I am filling my HTML table with data genericItems

. The table is modifiable. There is a reset button to undo all changes made with backUpData

. This array is populated by the service:

getGenericItems(selected: Item) {
this.itemService.getGenericItems(selected).subscribe(
  result => {
     this.genericItems = result;
  });
     this.backupData = this.genericItems.slice();
  }

      

My idea was that the user changes would be reflected in the first array and the second array could be used as a backup for the reset operation. The problem I'm running into here is that the user is modifying the table ( genericItems[])

, the second array is backupData

also changing).

How does this happen and how to prevent it?

+42


source to share


13 replies


Try the following:

Clone array:

const myClonedArray  = Object.assign([], myArray);

      



Clone object:

const myClonedObject = Object.assign({}, myObject);

      

+92


source


Cloning arrays and objects in JavaScript has a different syntax . Sooner or later, everyone learns the difference and ends up here.

In Typescript and ES6, you can use the spread operator for array and object:

const myClonedArray  = [...myArray];  // This is ok for [1,2,'test','bla']
                                      // But wont work for [{a:1}, {b:2}]. 
                                      // A bug will occur when you 
                                      // modify the clone and you expect the 
                                      // original not to be modified.
                                      // The solution is to do a deep copy
                                      // when you are cloning an array of objects.

      

To make a deep copy of an object, you need an external library:

import * as cloneDeep from 'lodash/cloneDeep';
const myClonedArray = cloneDeep(myArray);     // This works for [{a:1}, {b:2}]

      



The spread operator also works on an object, but it will only make a shallow copy (first layer of children)

const myShallowClonedObject = {...myObject};   // Will do a shallow copy
                                               // and cause you an un expected bug.

      

To make a deep copy of an object, you need an external library:

 import * as cloneDeep from 'lodash/cloneDeep';
 const deeplyClonedObject = cloneDeep(myObject);   // This works for [{a:{b:2}}]

      

+29


source


Using a map or other similar solution will not help you deeply clone an array of objects. An easier way to do this without adding a new library is to use JSON.stringfy and then JSON.parse.

In your case, this should work:

this.backupData = JSON.parse(JSON.stringify(genericItems));

      

+8


source


the easiest way to clone an array is

backUpData = genericItems.concat();

      

This will create new memory for array indices

+2


source


Try the following:

[https://lodash.com/docs/4.17.4#clone][1]

var objects = [{ 'a': 1 }, { 'b': 2 }];

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

      

+1


source


The next line of your code creates a new array, copies all object references from genericItems

into this new array, and assigns it backupData

:

this.backupData = this.genericItems.slice();

      

So, while backupData

and genericItems

are different arrays, they contain the same exact object references.

You can bring a deep copy library for you (as @LatinWarrior mentioned).

But if Item

not too complicated, maybe you can add a method to it clone

to deep clone an object:

class Item {
  somePrimitiveType: string;
  someRefType: any = { someProperty: 0 };

  clone(): Item {
    let clone = new Item();

    // Assignment will copy primitive types

    clone.somePrimitiveType = this.somePrimitiveType;

    // Explicitly deep copy the reference types

    clone.someRefType = {
      someProperty: this.someRefType.someProperty
    };

    return clone;
  }
}

      

Then call clone()

for each element:

this.backupData = this.genericItems.map(item => item.clone());

      

+1


source


Below code can help you to copy first level objects

let original = [{ a: 1 }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))

      

, so for the case below, the values ​​remain unchanged

copy[0].a = 23
console.log(original[0].a) //logs 1 -- value didn't change voila :)

      

Crash for this case

let original = [{ a: {b:2} }, {b:1}]
const copy = [ ...original ].map(item=>({...item}))
copy[0].a.b = 23;
console.log(original[0].a) //logs 23 -- lost the original one :(

      


Final tip:

I would say go for the lodash cloneDeep

API, which helps you copy objects within objects by completely dereferencing them from the original. It can be installed as a separate module.

Refer to the documentation: https://github.com/lodash/lodash

Custom package : https://www.npmjs.com/package/lodash.clonedeep

+1


source


I have the same problem with the primeNg DataTable parameter. After trying and crying, I fixed the problem using this code.

private deepArrayCopy(arr: SelectItem[]): SelectItem[] {
    const result: SelectItem[] = [];
    if (!arr) {
      return result;
    }
    const arrayLength = arr.length;
    for (let i = 0; i <= arrayLength; i++) {
      const item = arr[i];
      if (item) {
        result.push({ label: item.label, value: item.value });
      }
    }
    return result;
  }

      

To initialize the backup value

backupData = this.deepArrayCopy(genericItems);

      

To discard changes

genericItems = this.deepArrayCopy(backupData);

      

The magic bullet is to recreate the elements using {}

constructor calls instead. I've tried new SelectItem(item.label, item.value)

that doesn't work.

+1


source


It looks like you got it wrong about where you are making a copy of the array. Take a look at my explanation below and a slight modification to the code that should work, helping you reset the data to a previous state.

In your example, I see the following:

  • you are making a request to get shared items
  • after you get the data you set the results to this.genericItems
  • directly after that you set backupData as result

As I understand it, you do not want the 3rd item in this order?

Would it be better:

  • you are making a data request
  • create a backup of the current this.genericItems file
  • then set genericItems as the result of your request

Try the following:

getGenericItems(selected: Item) {
  this.itemService.getGenericItems(selected).subscribe(
    result => {
       // make a backup before you change the genericItems
       this.backupData = this.genericItems.slice();

       // now update genericItems with the results from your request
       this.genericItems = result;
    });
  }

      

0


source


It sounds like you want a Deep Copy of the object. Why not use it Object.assign()

? No libaries needed and its a one-liner :)

getGenericItems(selected: Item) {
    this.itemService.getGenericItems(selected).subscribe(
        result => {
            this.genericItems = result;
            this.backupDate = Object.assign({}, result); 
            //this.backupdate WILL NOT share the same memory locations as this.genericItems
            //modifying this.genericItems WILL NOT modify this.backupdate
        });
}

      

More on Object.assign()

: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

0


source


You can use the map function

 toArray= fromArray.map(x => x);

      

0


source


If your elements in the array are not primitive, you can use the spread operator for that.

this.plansCopy = this.plans.map(obj => ({...obj}));

      

Full answer: fooobar.com/questions/14831200 / ...

0


source


Try it

const returnedTarget = Object.assign(target, source);

      

and pass an empty array to the target

if complex objects work like this for me

$.extend(true, [], originalArray)

in case of an array

$.extend(true, {}, originalObject)

in case of object

-1


source







All Articles