Here we will identify Deep copy/ Deep clone methods and their behavior and performances in JavaScript and Typescript.                        

Introduction

What is Deep Copy

In JavaScript/Typescript, a deep copy is a copy of an object that creates a new instance of the object rather than simply creating a reference to the original object. Any changes to the copied object will not affect the original object and vice versa.

This Copy that will be a faithful reflection of the base object, with all attributes and fields at every nesting level into  new references.

Why Deep Copy

The purpose of a deep copy in Angular is to create a new instance of an object completely independent of the original object. This is useful when you want to change an object without affecting the original object, such as when working with data that should not be modified.

For example, when creating a new component in Angular, you may want to pass data to that component without modifying the original data. Using a deep copy ensures that any changes made to the data within the component do not affect the original data.

Additionally, when you have a large and complex data structure, using a deep copy can be useful to create a new copy of the data and keep the original data intact.

In general, it's a good practice to use deep copy when working with data in Angular to avoid unintended side-effects that could occur as a result of modifying the original data.

Deep Copy vs Shallow Copy

A shallow copy, on the other hand, creates a new instance of the object but it only copies the reference of the nested objects, not the nested objects themselves. This means that any changes made to the nested objects in the copied object will also affect the original object, and vice versa.

When we talk about Angular deep copy, Shallow copy cannot be forgotten. I will discuss Shallow Copy in a separate blog post in the future.


Deep Copy Methods

A deep copy can be created in several ways, including:

1.  Using JSON stringify function

This method uses the built-in JSON object to convert the object to a JSON string, and then parse it back into an object. This method is simple and works for most basic objects.

let obj2 = JSON.parse(JSON.stringify(obj));
JSON stringify function

Do not Use in

This does not work for objects with methods or circular references.
If your object contains: Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays or other complex types.

Best to use

Small object can be use easily. and this can be relatively fast for them

2.  Using lodash.cloneDeep(obj) method

This method uses the lodash library to create a deep copy of an object.

You need to install the lodash library first by running npm install lodash and then import it in your component or service where you want to use it:

import * as _ from 'lodash';

let obj2 = _.cloneDeep(obj);
lodash.cloneDeep(obj)

Warning

⚠️
This method can be slower than JSON method in large object.

Best to use

It can handle large objects and preserve all data types.  It is a good sign to use it.

3. 🔥🔥 @angular-devkit deepClone(obj) method🔥🔥

This method creates a deep copy of an object using the deepClone  method from the @angular-devkit/core package. It can handle large objects and preserve all data types. But it requires an additional package to be installed and imported.

@angular-devkit/build-angular
Angular Webpack Build Facade. Latest version: 15.1.1, last published: 3 days ago. Start using @angular-devkit/build-angular in your project by running `npm i @angular-devkit/build-angular`. There are 197 other projects in the npm registry using @angular-devkit/build-angular.
import { deepClone } from '@angular-devkit/core';

let obj2 = deepClone(obj);
@angular-devkit deepClone(obj)

Warning

⚠️
Since v12.0, unused by the Angular tooling. So this is currently deprecated.

Best to use

It can handle large objects and preserve all data types.
Comparatively, this method is faster than JSON and loadsh methods in large objects
Even deprecated, this would be my favorite method to deep copy JavaScript objects.

4.  Object.assign({}, obj) (nested properties are shallow copy)

This method uses the built-in Object.assign method to create a new object with the properties of the original object. This method is also simple and works for most basic objects, but it only creates a shallow copy.

let obj2 = Object.assign({}, obj);
Object.assign({}, obj)

Do not Use in

This is a shallow copy for the nested objects. So do not use for the large JavaScript objects.

It creates a shallow copy of the object and nested objects within the original object are still references to the original objects.

Best to use

Very fast method for the small objects.

Performance Comparison

I am created a sample Angular application and tested above 4 methods using a benchmark library.

Here I am used benchmark.js npm library to compare methods.

benchmark
A benchmarking library that supports high-resolution timers & returns statistically significant results.. Latest version: 2.1.4, last published: 6 years ago. Start using benchmark in your project by running `npm i benchmark`. There are 230 other projects in the npm registry using benchmark.

Performance results

Now lets check the performance comparisons.

Small Object (s) Large Object (s)
JSON.parse(JSON.stringify(obj)) 2.04783E-06 6.48E-06
Object.assign({}, obj) 7.39E-08 1.49E-07
lodash.cloneDeep(obj) 1.44E-06 7.59629E-06
devkit.deepCopy(obj) 6.23E-07 1.50492E-06

Here I am simplify this table into a barchart.

Summarizing with performances

According to the benchmark test, the fastest way to  Object.assign. Unfortunately it supports only shallow copy.

Both the JSON.parse(JSON.stringify(obj)) and @angular-devkit/core/src/utils/object.deepClone(obj) methods can be good choices for deep copying large objects, depending on the specific requirements of your application.

My opinion

Per the performance testing, 🔥Devkit 🔥 will be a good choice for large and small objects with comparatively speed than other options and preserve all data types. If you do not have a specific requirement and do not like 3rd party libraries, the 🔥JSON stringify function🔥 best option.

Source Code

Before run this code install dependent libraries.

  1. loadsh
  2. devkit
  3. benchmark.js in npm
import {Component} from '@angular/core';
import * as Benchmark from 'benchmark';
import * as _ from 'lodash';
import {deepCopy} from "@angular-devkit/core/src/utils/object";


@Component({
    selector: 'app-root',
    template: `
        <h1>Cloning Method Comparison</h1>
    `
})
export class AppComponent {


    ngOnInit() {
        var obj = {
            name: 'John Doe',
            age: 30,
            address: {
                city: 'New York',
                country: 'USA'
            }
        };

        const bench = new Benchmark.Suite('Cloning Method Comparison')
            .add('JSON.parse(JSON.stringify(obj))', () => {
                JSON.parse(JSON.stringify(obj))
            })
            .add('Object.assign({}, obj)', () => {
                Object.assign({}, obj)
            })
            .add('lodash.cloneDeep(obj)', () => {
                _.cloneDeep(obj)
            })
            .add(' deepCopy(obj)', () => {
                deepCopy(obj)
            })
            .on('cycle', (event: any) => {
                console.log(String(event.target));
            })
            .on('complete', function () {
                bench.forEach((o: any) => {
                    console.log(o.stats.mean);
                });
            })
            .run({async: true});


        let obj2 = {
            orderNumber: 12345,
            customer: {
                firstName: "John",
                lastName: "Doe",
                email: "john.doe@example.com",
                phone: "555-555-5555"
            },
            shippingAddress: {
                street: "123 Main St",
                city: "Anytown",
                state: "CA",
                zip: "12345"
            },
            billingAddress: {
                street: "456 Park Ave",
                city: "Anytown",
                state: "CA",
                zip: "54321"
            },
            items: [
                {
                    itemName: "Item 1",
                    itemId: "123",
                    quantity: 2,
                    price: 19.99
                },
                {
                    itemName: "Item 2",
                    itemId: "456",
                    quantity: 1,
                    price: 9.99
                },
                {
                    itemName: "Item 3",
                    itemId: "789",
                    quantity: 3,
                    price: 29.99
                }
            ],
            total: 89.97,
            status: "Processing",
            placedOn: "2022-12-01T09:30:00Z"
        };

        const benchLargeObject = new Benchmark.Suite('Cloning Method Comparison')
            .add('JSON.parse(JSON.stringify(obj))', () => {
                JSON.parse(JSON.stringify(obj2))
            })
            .add('Object.assign({}, obj)', () => {
                Object.assign({}, obj2)
            })
            .add('lodash.cloneDeep(obj)', () => {
                _.cloneDeep(obj2)
            })
            .add('devkit.deepCopy(obj)', () => {
                deepCopy(obj)
            })
            .on('cycle', (event: any) => {
                console.log(String(event.target));
            })
            .on('complete', function() {
                benchLargeObject.forEach((o: any) => {
                    console.log(o.stats.mean);
                } );
            })
            .run({ async: true });
    }
}
How do I benchmark above methods

Resources

  1. What is the best and most efficient way to deep clone an object in JavaScript?
  2. Angular deep copy vs shallow copy 🔥

📨
Put your comments on this topic. See ya soon on another blog post. 👊👊
Share this post