Communities

Writing
Writing
Codidact Meta
Codidact Meta
The Great Outdoors
The Great Outdoors
Photography & Video
Photography & Video
Scientific Speculation
Scientific Speculation
Cooking
Cooking
Electrical Engineering
Electrical Engineering
Judaism
Judaism
Languages & Linguistics
Languages & Linguistics
Software Development
Software Development
Mathematics
Mathematics
Christianity
Christianity
Code Golf
Code Golf
Music
Music
Physics
Physics
Linux Systems
Linux Systems
Power Users
Power Users
Tabletop RPGs
Tabletop RPGs
Notifications
Mark all as read
Q&A

How to deeply clone an array in Angular / TypeScript?

+5
−0

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?

Why does this post require moderator attention?
You might want to add some details to your flag.
Why should this post be closed?

0 comment threads

2 answers

+8
−0

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.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

+2
−0

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.

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.

Why does this post require moderator attention?
You might want to add some details to your flag.

0 comment threads

Sign up to answer this question »