Blog is available in backup mode, go to github-pages for newer posts

Back to articles list

Deep clone in AngularJS

TL;DR

angular.copy from AngularJS 1.x is quadratic-slow on large objects, especially in IE.

Use JSON.stringify/JSON.parse for simple objects or _.deepClone for objects with Date objects or cyclic references. Actually, if you don't clone objects of more than 1000 nodes, this shouldn't bother you.

How I bumped into it

The other day I faced with a need to deeply clone an object from the redux store and since we're using angular I initially thought about angular.copy, but then I remembered, that its use is being discouraged because of "being very slow in IE".

Maybe, it was an old bug an now it's fixed? A quick search showed that there's an issue on github and it's still open.

OK, but how exactly slow is that function? People report it being slow on large objects. I quickly created a generator of random objects of certain nesting depth and total node count, and a suite to test function. Here's a gist with everything combined. In order to measure one needs to run suite with deep cloning function passed as an argument.

Other options?

So angular.copy turned out to be really slow. Even though it was OK for the case I started with, I went too far to stop researching the question. What other options do we have? Of course, I can write a stupid recursive cloner in ~10 lines of code and call it a day, but since I'm an ENTERPRISE developer and that day I didn't feel like writing my own solution, I considered existing libraries and really trivial methods, which could be written in a single line.

Actually, somewhere near the end of writing that post, I implemented simple cloner, and ... discovered nothing new. It's faster than much more elaborate _.deepClone, but not that much. You can see a green line on the chart for the Chrome below.

So I stuck with the following four methods:

The last option is the most interesting one. The thing is browsers have built-in support for a magic called structured cloning of objects, but it's not directly accessible via JavaScript. It is hidden inside postMessage API, so we just need to send a message to the same window. The only problem I expected was that such a function would be asynchronous. Alas, API call blows up with an error on function properties. And what's worse, it seems to leak memory in IE: the full suite failed with "out of memory" so I had to run it in 3 parts. During tests, I just used postMessage w/o getting it back. That was enough to measure cloning process. More or less proper implementation is available in the same gist.

Quality

method cycles non-trivial types preserves prototypes functions
angular.copy
_.cloneDeep 1
JSON 🛑
postMessage 🛑

Quantity

Here are the charts. Average time in milliseconds by the number of nodes in an object.

Chrome results chart

FireFox results chart

Internet Explorer results chart

Reference

Test env

I also checked angular.copy in Safari on @sublimeye's Mac, but results are uninterestingly similar to Chrome apart from his CPU (i7-4770HQ @2.2GHz) is significantly faster than mine. Edge is almost the same as IE.

Excel spreadsheet with data

Footnotes

1 — documentation for clone says that functions are transformed into empty objects and cloneDeep works the same. But that's true only if we pass a function directly to the cloneDeep. Any nested function property is just copied by a normal =.