Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
How to deeply clone an array in Angular / TypeScript?
I have an array that I need to clone in Angular / Typescript. That is, any change done on an element from the cloned array should not affect the content of the initial array.
How can I achieve this?
2 answers
These can be achieved in several ways, but there might be some drawbacks:
- through JSON serialization/deserialization:
const cloned = JSON.parse(JSON.stringify(array));
The advantage is that it works for any type of object, but there are some drawbacks such as converting some values like undefined
to empty objects {}
.
It also requires custom code when dealing with cyclic references within the objects hierarchy.
- using Spread syntax:
this.clonedArray = theArray.map(e => ({ ... e }));
- using a custom library such as lodash:
var deep = _.cloneDeep(array);
. It also support arbitrary objects, not just arrays.
0 comment threads
By "deeply clone", I assume you mean "also make copies of whatever nested structures the object might have". And for those, I guess libraries like Lodash are more reliable and appropriate if you want to save some work.
Of course for simple cases (such as arrays containing only numbers, strings, or even nested arrays/objects whose values are numbers and strings), approaches such as JSON.parse(JSON.stringify(array))
are enough. Actually, if the array contains only "simple" types such as numbers and strings and has no nested structures, array.slice()
is enough to make a copy.
But for more complicated cases, Lodash can save you a lot of work. JSON approach, for instance, doesn't work if the array contains a function or some other objects, such as a Date
. And the map
solution suggested in the other answer doesn't work as well, as it converts nested arrays to objects:
var x = [ 1, 2, {a: 3} ];
var array = [ function () { console.log('hi') }, new Date(), x ];
function checkClone(clone, msg) {
console.log(`------\n${msg}`);
console.log(clone);
// check function
console.log('is function?', typeof clone[0] === 'function');
// check Date
console.log('is Date?', Date.prototype.isPrototypeOf(clone[1]));
// check array
if (Array.isArray(clone[2])) {
// add element to nested array, and check if original array is modified
clone[2].push(3);
console.log('original array', x);
} else {
console.log('nested element is not an array');
}
}
checkClone(_.cloneDeep(array), 'Lodash');
checkClone(array.map(e => ({ ...e })), 'map');
checkClone(JSON.parse(JSON.stringify(array)), 'JSON');
I've created a function that receives a cloned array and checks what happened with its elements. The output is:
------
Lodash
[ [Function (anonymous)], 2022-01-07T17:40:25.897Z, [ 1, 2, { a: 3 } ] ]
is function? true
is Date? true
original array [ 1, 2, { a: 3 } ]
------
map
[ {}, {}, { '0': 1, '1': 2, '2': { a: 3 } } ]
is function? false
is Date? false
nested element is not an array
------
JSON
[ null, '2022-01-07T17:40:25.897Z', [ 1, 2, { a: 3 } ] ]
is function? false
is Date? false
original array [ 1, 2, { a: 3 } ]
As we can see, only Lodash correctly copied everything, keeping the function and the Date
. JSON solution didn't keep those (the function was ignored, and the Date
was converted to a string representation). map
solution is even worse, because it also converted the nested array to an object.
Another drawbacks: JSON doesn't handle cyclic references and undefined
values will be changed to null
. map
solution can handle cycles, but undefined
is changed to {}
(empty object). Neither works with custom classes (Lodash handles all those cases correctly, though).
If you're cloning "simple" objects (no functions or any types other than numbers and strings, no null
/undefined
, etc), I guess the JSON approach should be fine. If you have anything else (Date
, BigInt
, Symbol
, your custom class, etc), I'd say to go with Lodash (or any other library), which will be easier than writing your own clone function (just to have an idea of the difficulty, take a look at Lodash source code).
But if you have just one or another element that's not handled by JSON.stringify
and JSON.parse
, maybe you don't need to import a whole library just for that. You could only add a custom serialization and deserialization method for this element. For example, if the only "different", non-JSON-supported type is a Date
:
// custom serialization for Dates
Date.prototype.toJSON = function() {
return '__date__:' + this.getTime();
}
// custom deserialization for Date (anything else is treated as is)
function deserialize(key, val) {
if (val && typeof val === 'string' && val.indexOf('__date__:') === 0) {
return new Date(parseInt(val.slice(9)));
}
return val;
}
var array = [1, 'abc', new Date()];
console.log(JSON.parse(JSON.stringify(array), deserialize));
But if you need to handle lots of different types (such as functions, custom classes, etc), this method can become tedious. In this case, you could evaluate if it's worth writing your own functions, or use a library like Lodash.
0 comment threads