Deep copying objects in Angular

Viewed 98791

AngularJS has angular.copy() to deep copy objects and arrays.

Does Angular also have something like that?

12 Answers

You can also use:

JSON.parse(JSON.stringify(Object))

if it's on your scope, it's in every Angular component, directive, etc. and it's also on every node environment.

Unless you have a circular reference, it should work and will effectively dissociate your variable reference to the original object.

You can deep copy an object in Angular by using lodash's cloneDeep method:

Install lodash with yarn add lodash or npm install lodash.

In your component, import cloneDeep and use it:

import * as cloneDeep from 'lodash/cloneDeep';
...
clonedObject = cloneDeep(originalObject);

It's only 18kb added to your build, well worth for the benefits.

I've also written an article here, if you need more insight on why using lodash's cloneDeep.

Create helper class with name deepCopy.ts

/*
* DeepCopy class helps to copy an Original Array or an Object without impacting on original data
*/

export class DeepCopy {

  static copy(data: any) {
    let node;
    if (Array.isArray(data)) {
      node = data.length > 0 ? data.slice(0) : [];
      node.forEach((e, i) => {
        if (
          (typeof e === 'object' && e !== {}) ||
          (Array.isArray(e) && e.length > 0)
        ) {
          node[i] = DeepCopy.copy(e);
        }
      });
    } else if (data && typeof data === 'object') {
      node = data instanceof Date ? data : Object.assign({}, data);
      Object.keys(node).forEach((key) => {
        if (
          (typeof node[key] === 'object' && node[key] !== {}) ||
          (Array.isArray(node[key]) && node[key].length > 0)
        ) {
          node[key] = DeepCopy.copy(node[key]);
        }
      });
    } else {
      node = data;
    }
    return node;
  }
}

Import deepCopy file where ever you required and use as below code DeepCopy.copy(arg); , Here arg would either object or array which you want

Some modifications for KrishnamrajuK's answer

export class DeepCopy {
  static copy(data: any, objMap?: WeakMap<any, any>) {
    if (!objMap) {
      // Map for handle recursive objects
      objMap = new WeakMap();
    }

    // recursion wrapper
    const deeper = value => {
      if (value && typeof value === 'object') {
        return DeepCopy.copy(value, objMap);
      }
      return value;
    };

    // Array value
    if (Array.isArray(data)) return data.map(deeper);

    // Object value
    if (data && typeof data === 'object') {
      // Same object seen earlier
      if (objMap.has(data)) return objMap.get(data);
      // Date object
      if (data instanceof Date) {
        const result = new Date(data.valueOf());
        objMap.set(data, result);
        return result;
      }
      // Use original prototype
      const node = Object.create(Object.getPrototypeOf(data));
      // Save object to map before recursion
      objMap.set(data, node);
      for (const [key, value] of Object.entries(data)) {
        node[key] = deeper(value);
      }
      return node;
    }
    // Scalar value
    return data;
  }
}

If the source is an array of objects, using map:

let cloned = source.map(x => Object.assign({}, x));

OR

let cloned = source.map((x) => {
                return { ...x };
             });

I have created a very simple function in typescript which accepts all possible inputs and gives the deep cloned copy of that object.

Hope it will help somebody.

  public deepCopy(obj) {

    var clonedObject: any;

    if (obj instanceof Array) {
        var itemArray = Object.assign([], obj);
        clonedObject = itemArray;

        for (var j = 0; j < clonedObject.length; j++) {
            clonedObject[j] = this.deepCopy(clonedObject[j]);
        }

        return clonedObject;
    }
    else if (typeof obj === 'number' || typeof obj == 'string') {
        return obj
    }
    else {


        var item = Object.assign({}, obj);
        clonedObject = item;

        let allKeys = Object.keys(clonedObject);

        for (var i = 0; i < allKeys.length; i++) {
            if (clonedObject[allKeys[i]] instanceof Array) {
                //If the calue is Array
                clonedObject[allKeys[i]] = this.deepCopy(clonedObject[allKeys[i]]);
            }
            else if (clonedObject[allKeys[i]] instanceof Date) {
                clonedObject[allKeys[i]] = new Date(clonedObject[allKeys[i]].valueOf());
            }
            else if (clonedObject[allKeys[i]] instanceof Object){
                //if the value is JOBJECT.
                clonedObject[allKeys[i]] = this.deepCopy(clonedObject[allKeys[i]]);
            }
        }
        return clonedObject;
    }


}

I am faced with the problem of deep copying. angular.copy({}, factory) and angular.extend({}, factory) helps well for array or hashes objects, but when copying an object a calass, sometimes there may be problems with connected dependencies. I solved this problem so:

 copyFactory = (() ->
    resource = ->
      resource.__super__.constructor.apply this, arguments
      return
    this.extendTo resource
    resource
  ).call(factory)

You can try this: https://www.npmjs.com/package/ngx-scv-util

I faced the same issue and created an npm package for the same. I know its an overhead just for this functionality :) . I plan to add more to this package over time. This one has been tested on 10.1.6 version of Angular. It should definitely work with versions higher than 10.1.6. Usage details are mentioned on the npm package page.

Some of the answers here rely on lodash which can cause issues when imported in angular 10+ with a warning message like the following:

WARNING in xxxxx.ts depends on lodash/cloneDeep. CommonJS or AMD dependencies can cause optimization bailouts.

Other answers use parsing via JSON or try to roll their own implementation. Not to roll ours, we use clone: a lightweight clone utility with limited dependencies.

In order to use clone, you just need to install these two packages:

npm install clone
npm install --save-dev @types/clone

The create a service (the service is not mandatory, but I prefer this approach) that uses the clone API:

import { Injectable } from '@angular/core';
import * as clone from 'clone';

@Injectable()
export class ObjectCloneService {
    public cloneObject<T>(value: T): T {
        return clone<T>(value);
    }
}

Remember to add the service to your module.

To use cloneDeep without any "CommonJS or AMD dependencies can cause optimization bailouts.", you can use it like this:

npm i lodash-es --save

Then simply in any component:

import { cloneDeep } from 'lodash-es';
// ...
let a = cloneDeep(b);
Related