158

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?

Arun
  • 3,701
  • 5
  • 32
  • 43
  • 1
    Looks like you made a shallow copy of the array. It sounds like you're modifying the objects they were holding and seeing the changes. You need to make a deep copy or come up with a different way to represent your data. – Jeff Mercado Jun 28 '17 at 17:22
  • 1
    They are pointing to the same reference. If you return a new array using a library like lodash or similar, you won't have that problem. – Latin Warrior Jun 28 '17 at 17:24
  • `slice()` will create new object from another array I guess... – Arun Jun 28 '17 at 17:25
  • 1
    The second array is being modified because rather than creating a new array you are merely referencing the original one. If your using type script and ES6 you can create a copy like this this.backupData = [...this.genericItems] this will create a copy of the array. Hope that helps! – Molik Miah Jun 28 '17 at 17:26
  • slice() would work, yes. At any rate, what you need to do is use functional programming and return a new array that does not modify or point to the original one. – Latin Warrior Jun 28 '17 at 17:29
  • @MolikMiah I do not believe that is correct. `genericItems.slice()` creates a new array, but it contains the exact same objects. So `[...this.genericItems]` would effectively do the same thing, no? – Frank Modica Jun 28 '17 at 17:29
  • slice should work, but only for one-dimensional array. If you two-dimensional array it is not gonna work. – Charlie Ng Jun 28 '17 at 17:30
  • No it's one dimensional array – Arun Jun 28 '17 at 17:33
  • @FrankModica you're right, both methods should create a copy of the array rather than reference the original array. it is strange that the .slice() is not working! Arun Raj - if you're still having trouble can you please give us an example of what the data looks like in the array and when you say backupData gets modified, in what way does it get modified? – Molik Miah Jun 28 '17 at 17:36
  • 3
    @MolikMiah I'm saying that `slice` takes an array and copies each reference into a new array. So the old array and new array are actually different, but the objects inside are exactly the same. So it should be the same as doing `[...array]` – Frank Modica Jun 28 '17 at 17:38
  • can you show us the html ? can you make sure the user is modifying genericItems[], not backupData[]? That is the only mistake I can think of. – Charlie Ng Jun 28 '17 at 17:38

16 Answers16

252

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)));
abahet
  • 10,355
  • 4
  • 32
  • 23
  • 15
    If your array is an array of objects, (not primitive types), then you need to go one level deeper with your shallow copy. For me the solution was to iterate through the array and clone the objects. I.e. `const myArray= [{ a: 'a', b: 'b' }, { a: 'c', b: 'd' }]; const myClonedArray = []; myArray.map(val => myClonedArray.push(Object.assign({}, val)));`. An alternative solution for a proper deep copy would be for JSON serialization as mentioned in other answers. – mumblesNZ Dec 05 '18 at 22:37
  • 3
    @mumblesNZ, if you're really talking about a deep copy, two levels won't suffice either. You'd have to use something like Lodash's `_.cloneDeep(obj)`. JSON serialization would work, like you said, but that's a very roundabout way to do it. – user2683747 Nov 20 '19 at 16:45
  • 1
    The option 2 part of this answer works fine for an object that holds primitives. If an array element contains a value which is an array or object, deep copy helps as @user2683747 mentioned. – Deepak Jan 27 '21 at 14:47
  • 1
    A helpful reference about deep and shallow copy.. https://www.techmeet360.com/blog/playing-with-javascript-object-clone/ – Deepak Jan 27 '21 at 14:48
145

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}}]
David Dehghan
  • 22,159
  • 10
  • 107
  • 95
37

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));
jmuhire
  • 2,091
  • 21
  • 19
18

try the following code:

this.cloneArray= [...this.OriginalArray]
Raj Baral
  • 661
  • 6
  • 19
Gaurav Panwar
  • 974
  • 10
  • 11
6

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());
Frank Modica
  • 10,238
  • 3
  • 23
  • 39
6

Array copy explained - Deep & Shallow

Below code might help you to copy the first level objects

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

so for below case, values remains intact

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

Fails 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 {b: 23} -- lost the original one :(

Try lodash separate ES module - cloneDeep:

I would say go for lodash cloneDeep API ( This can be installed as a separate module, reduced code footprint for treeshaking ) which helps you to copy the objects inside objects completely dereferencing from original one's. As another option you can rely on JSON.stringify & JSON.parse methods to dereference quickly and performant too.

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

Individual Package : https://www.npmjs.com/package/lodash.clonedeep

NiRUS
  • 3,901
  • 2
  • 24
  • 50
5

you can use map function

 toArray= fromArray.map(x => x);
Amirouche Zeggagh
  • 3,428
  • 1
  • 25
  • 22
5

Clone an object / array (without reference) in a very powerful way

You can get deep-copy of your object / array using @angular-devkit.

import { deepCopy } from '@angular-devkit/core/src/utils/object';

export class AppComponent {

  object = { .. some object data .. }
  array = [ .. some list data .. ]

  constructor() {
     const newObject = deepCopy(this.object);
     const newArray = deepCopy(this.array);
  }
}
WasiF
  • 26,101
  • 16
  • 120
  • 128
  • 2
    Excellent, this works, a native solution for people who don't prefer or have `loaddash` dependency – HariHaran Oct 04 '21 at 07:25
  • 1
    That did it, thank you very much. I'm amazed as to why the spread operator or other methods like slice, etc. didn't work. assigning the destination array to the source array, always changed the source array too, what a disaster. – Nexus Dec 22 '21 at 22:59
3

I have the same issue with primeNg DataTable. After trying and crying, I've fixed the issue by 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;
  }

For initializing backup value

backupData = this.deepArrayCopy(genericItems);

For resetting changes

genericItems = this.deepArrayCopy(backupData);

The magic bullet is to recreate items by using {} instead of calling constructor. I've tried new SelectItem(item.label, item.value) which doesn't work.

mehdinf
  • 121
  • 5
2

the easiest way to clone an array is

backUpData = genericItems.concat();

This will create a new memory for the array indexes

Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
VarunJi
  • 685
  • 2
  • 14
  • 31
2

If your items in the array are not primitive you can use spread operator to do that.

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

Complete answer : https://stackoverflow.com/a/47776875/5775048

gatsby
  • 1,148
  • 11
  • 12
1

Try this:

[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
Latin Warrior
  • 902
  • 11
  • 14
1

It looks like you may have made a mistake as to where you are doing the copy of an Array. Have a look at my explanation below and a slight modification to the code which should work in helping you reset the data to its previous state.

In your example i can see the following taking place:

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

Am i right in thinking you don't want the 3rd point to happen in that order?

Would this be better:

  • you do the data request
  • make a backup copy of what is current in this.genericItems
  • then set genericItems as the result of your request

Try this:

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;
    });
  }
Molik Miah
  • 132
  • 7
1

array level cloning solutions didn't work for me, added this clone utility in typescript which does a deeper clone that worked in my case

export function cloneArray<T>(arr: T[]): T[] {
  return arr.map((x) => Object.assign({}, x))
}

by "didn't work" i meant, that if i passed an array to a function [...arr] or Object.assign([], arr) or arr.splice() it was still mutating the original array

Sonic Soul
  • 23,855
  • 37
  • 130
  • 196
0

Looks like what you want is Deep Copy of the object. Why not use 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

CozyAzure
  • 8,280
  • 7
  • 34
  • 52
  • This will do the same thing as `slice()`. It creates a new array, but copies the object references from the old array. – Frank Modica Jun 28 '17 at 18:51
  • Also, I think it should be `Object.assign([], result)`. Otherwise, I think you'll lose the `length` property (and maybe some other things). – Frank Modica Jun 28 '17 at 18:54
-1

Try this

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

and pass empty array to target

in case complex objects this way works for me

$.extend(true, [], originalArray) in case of array

$.extend(true, {}, originalObject) in case of object

Samir Ghoneim
  • 591
  • 1
  • 6
  • 14