Ankit Singh
Ankit Singh

Reputation: 24945

Deep copying objects in Angular

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

Does Angular also have something like that?

Upvotes: 103

Views: 130461

Answers (12)

Smokovsky
Smokovsky

Reputation: 579

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

Upvotes: 0

Yennefer
Yennefer

Reputation: 6234

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.

Upvotes: 1

Sharath
Sharath

Reputation: 606

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.

Upvotes: 0

Krishnamraju K
Krishnamraju K

Reputation: 121

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

Upvotes: 7

vp_arth
vp_arth

Reputation: 14992

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

Upvotes: 6

Viktor Ivliiev
Viktor Ivliiev

Reputation: 1334

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)

Upvotes: 0

Andrea Ialenti
Andrea Ialenti

Reputation: 4560

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

Upvotes: 23

Anil Kumar
Anil Kumar

Reputation: 21

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


}

Upvotes: 0

Lahar Shah
Lahar Shah

Reputation: 7654

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

Upvotes: 1

BogdanC
BogdanC

Reputation: 2784

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.

Upvotes: 23

martin
martin

Reputation: 96899

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?

Upvotes: 22

Gabriel Balsa Cant&#250;
Gabriel Balsa Cant&#250;

Reputation: 2012

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.

Upvotes: 134

Related Questions