I have array of two objects:
genericItems: Item[] = [];
backupData: Item[] = [];
I am populating my HTML table with genericItems
data. 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?
This question is related to
arrays
angular
typescript
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 which helps you to copy the objects inside objects completely dereferencing from original one's. This can be installed as a separate module.
Refer documentation: https://github.com/lodash/lodash
Individual Package : https://www.npmjs.com/package/lodash.clonedeep
try the following code:
this.cloneArray= [...this.OriginalArray]
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());
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
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
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));
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.
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:
Am i right in thinking you don't want the 3rd point to happen in that order?
Would this be better:
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;
});
}
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
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.
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
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}}]
you can use map function
toArray= fromArray.map(x => x);
the easiest way to clone an array is
backUpData = genericItems.concat();
This will create a new memory for the array indexes
Source: Stackoverflow.com