Cloning an array in Javascript/Typescript

I have array of two objects:

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

I am populating my HTML table with genericItemsdata. The table is modifiable. There is a reset button to undo all changes done with backUpData. This array is populated by a 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 will get reflected in first array and second array can be used as backup for reset operation. The issue I am facing here is when the user modifies the table (genericItems[]) the second array backupData also gets modified.

How is this happening and how to prevent this?


Solution 1:

Clone an object:

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

Clone an Array:

  • Option 1 if you have an array of primitive types:

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

  • Option 2 - if you have an array of objects:
const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }];
const myClonedArray = [];
myArray.forEach(val => myClonedArray.push(Object.assign({}, val)));

Solution 2:

Cloning Arrays and Objects in javascript have a different syntax. Sooner or later everyone learns the difference the hard way and end 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 do a deep copy of an object you need an external library:

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

The spread operator works on object as well but it will only do a shallow copy (first layer of children)

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

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

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

Solution 3:

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

In your case this should work :

this.backupData = JSON.parse(JSON.stringify(genericItems));
  • When using the last version of JS/TS, installing a large library like lodash for just one/two function is a bad move. You will heart your app performance and in the long run you will have to maintain the library upgrades! check https://bundlephobia.com/[email protected]
  • For small objet lodash cloneDeep can be faster but for larger/deeper object json clone become faster. So in this cases you should not hesitate to use it. check https://www.measurethat.net/Benchmarks/Show/6039/0/lodash-clonedeep-vs-json-clone-larger-object and for infos https://v8.dev/blog/cost-of-javascript-2019#json

  • The inconvenient is that your source object must be convertible to JSON.

Solution 4:

try the following code:

this.cloneArray= [...this.OriginalArray]

Solution 5:

The following line in your code creates a new array, copies all object references from genericItems into that new array, and assigns it to backupData:

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

So while backupData and genericItems are different arrays, they contain the same exact object references.

You could bring in a library to do deep copying for you (as @LatinWarrior mentioned).

But if Item is not too complex, maybe you can add a clone method to it to deep clone the object yourself:

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() on each item:

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