96

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

Does Angular also have something like that?

Lazar Ljubenović
  • 18,976
  • 10
  • 56
  • 91
Ankit Singh
  • 24,525
  • 11
  • 66
  • 89

12 Answers12

130

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.

Gabriel Balsa Cantú
  • 1,954
  • 1
  • 14
  • 11
  • 1
    This seems like a very simple and effective deep copy! Why is this not scoring higher? Is something wrong with this answer? – TSG Nov 16 '17 at 15:08
  • For a deep copying solution that will remove all references this is the best method, available on Browser/CommonJS, etc. I use it in production when an object snapshot is needed. I guess I arrived to answer a little late, like 1 year too late. @TSG – Gabriel Balsa Cantú Nov 21 '17 at 22:01
  • 4
    Also, if you're using an object literal with functions on it (not just values) they will be left out, but you can easily combine `Object.assing({}, oldObject, JSON.parse(JSON.stringify(oldObject)))` and this will repopulate your function properties from your object literal and with JSON will create a deep copy without any relation to original. – Gabriel Balsa Cantú Nov 21 '17 at 22:11
  • Best answer in my opinion – Johannes Wanzek Aug 29 '18 at 10:15
  • 11
    Beware, however, that this doesn't work when there are some Dates. – František Žiačik Dec 18 '18 at 22:10
  • 1
    @FrantišekŽiačik Dates would be formatted as JSON types, which is the similar behavior you get when receiving a Rest response. As a basic behavior it's simple and zero dependency, for a more complete solution it'd be advisable to complement this as a factory function to recover JSON date strings back to date objects. It's a matter of *adapt to needs* instead of *one size fits all* :) – Gabriel Balsa Cantú Dec 22 '18 at 01:24
  • It is not working with object of array – Om PandeY Sep 23 '22 at 08:21
23

Another option is to implement your own function:

/**
 * Returns a deep copy of the object
 */
public static deepCopy(oldObj: any) {
    var newObj = oldObj;
    if (oldObj && typeof oldObj === "object") {
        if (oldObj instanceof Date) {
           return new Date(oldObj.getTime());
        }
        newObj = Object.prototype.toString.call(oldObj) === "[object Array]" ? [] : {};
        for (var i in oldObj) {
            newObj[i] = this.deepCopy(oldObj[i]);
        }
    }
    return newObj;
}
jcubic
  • 61,973
  • 54
  • 229
  • 402
Andrea Ialenti
  • 4,380
  • 2
  • 19
  • 22
  • 1
    I am not able to deep copy dates using this method – A_J Nov 06 '17 at 22:31
  • I've added clone of Date based on [How to clone a Date object in JavaScript](https://stackoverflow.com/a/1090817/387194) – jcubic Feb 19 '19 at 08:23
22

This question isn't a duplicate of How can I use angular.copy in angular 2 because the OP is asking about deep copying objects. The linked answer recommends Object.assign() which doesn't make a deep copy.

Actually, using Angular2 doesn't restrict you from using other libraries like jQuery for deep copying objects with their $.clone() function or lodash with _.cloneDeep().

The most common libraries have their typings available via typings CLI tools so even when transpiling from TypeScript you can seamlessly use anything you want.

Also see: What is the most efficient way to deep clone an object in JavaScript?

martin
  • 93,354
  • 25
  • 191
  • 226
  • Object.assign() doesn't do deep copy when there are nested objects within the targeted object, so as a naive solution you may go through the targeted object and do Object.assign() for every property of it! It worked for me when I didn't want to have a dependency on any third-party library – Dany Wehbe Nov 06 '18 at 11:10
21

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.

BogdanC
  • 2,746
  • 2
  • 24
  • 22
  • 1
    the copying by value didn't work for me using Lodash. Do you maybe know why? I am still getting referenced values, so they both change at the same time, if I change one. – Vladimir Despotovic Jul 20 '18 at 15:24
7
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

Krishnamraju K
  • 111
  • 1
  • 2
  • 8
  • 1
    This actually the best answer. It effectively deep copies data without importing external libraries. I don't want to bring in a whole library to only use one function (even if it's only 18KB). – Frozenfrank Jun 26 '19 at 17:38
  • The best answer is this..! Awosome!! – K L P Apr 03 '21 at 16:32
  • 5
    I wouldn't blindly copy this snippet. It works, but with flaws. Within the array and the object assertion there is duplicate code. Within that duplicate code this `e!=={}` will always be true, and since `typeof [] === 'object'`, the part after the OR-pipes will never work as intended. This will also simply copy any dates it encounters. The first part of that ternary would be better looking like `? new Date(data) :`. – Sjeiti Jul 05 '21 at 19:59
4

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;
  }
}

vp_arth
  • 14,461
  • 4
  • 37
  • 66
1

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 };
             });
Lahar Shah
  • 7,032
  • 4
  • 31
  • 39
1

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.

Yennefer
  • 5,704
  • 7
  • 31
  • 44
0

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;
    }


}
Anil Kumar
  • 11
  • 1
0

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)
Viktor Ivliiev
  • 1,015
  • 4
  • 14
  • 21
0

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.

Sharath
  • 516
  • 1
  • 8
  • 19
0

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);
Smokovsky
  • 569
  • 1
  • 7
  • 21