Velvet Star Monitor

Standout celebrity highlights with iconic style.

updates

Cloning an array in Javascript/Typescript

Writer Matthew Martinez

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?

12

15 Answers

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)));
4

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}}]
5

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
  • 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 and for infos

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

1

try the following code:

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

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());

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 23 -- lost the original one :(

Final advice:

I would say go for lodash cloneDeep API ( This can be installed as a separate module ) which helps you to copy the objects inside objects completely dereferencing from original one's.

Refer documentation:

Individual Package :

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.

0

you can use map function

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

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); }
}
2

the easiest way to clone an array is

backUpData = genericItems.concat();

This will create a new memory for the array indexes

1

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 :

Try this:

[
var objects = [{ 'a': 1 }, { 'b': 2 }];
var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true
0

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; }); }
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():

2

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

Your Answer

Sign up or log in

Sign up using Google Sign up using Facebook Sign up using Email and Password

Post as a guest

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy