160

How can I copy an object and lose its reference in Angular?

With AngularJS, I can use angular.copy(object), but I'm getting some error using that in Angular.

EXCEPTION: ReferenceError: angular is not defined

4
  • Check this solution it might help: Link Commented Jan 5, 2017 at 1:29
  • In many situations, you might want to use .copy() but actually wouldnt need it. In various AngJS1 projects I have seen, it was an overkill, where a manual copy of the relevant substructures would have made for a cleaner code. Maybe that was part of the decision not to implement it by the Angular team. Commented Feb 4, 2017 at 21:47
  • by the way, related (and also unanswered): stackoverflow.com/questions/41124528/… Commented Feb 4, 2017 at 21:48
  • Possible duplicate of What is the most efficient way to deep clone an object in JavaScript? Commented Oct 3, 2019 at 7:24

16 Answers 16

212

Assuming you are using ES6, you can use var copy = Object.assign({}, original). Works in modern browsers; if you need to support older browsers check out this polyfill

update:

With TypeScript 2.1+, ES6 shorthand object spread notation is available:

const copy = { ...original }
Sign up to request clarification or add additional context in comments.

15 Comments

Note that angular.copy() creates a deep copy contrary to Object.assign(). If you want deep copy use lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
in Webstorm I got Unresolved function or method assign() ; IDE Details: Webstorm 2016.2. How can I resolve that ?
@meorfi Go to File -> Settings -> Languages & Frameworks -> Javascript and set Javascript language version to ECMAScript 6.0.
@bertrandg _.clone(value) is different than angular.copy(), it will not create new instance, so as _.cloneDeep(value) it still create a reference stackoverflow.com/questions/26411754/…
Additionally, if you're copying an array, use: const copy = [ ...original ]
|
47

Till we have a better solution, you can use the following:

duplicateObject = <YourObjType> JSON.parse(JSON.stringify(originalObject));

EDIT: Clarification

Please note: The above solution was only meant to be a quick-fix one liner, provided at a time when Angular 2 was under active development. My hope was we might eventually get an equivalent of angular.copy(). Therefore I did not want to write or import a deep-cloning library.

This method also has issues with parsing date properties (it will become a string).

Please do not use this method in production apps. Use it only in your experimental projects - the ones you are doing for learning Angular 2.

10 Comments

this ruins your dates and is slow as hell.
Not as slow as importing an entire library to do a single task though, as long as what you're doing is pretty simple...
this is horrible, never use that
@MurhafSousli please try to understand the context of this answer. This was provided when Angular 2 was under development, and the hope was that we will eventually get an equivalent of angular.copy() function. To bridge the waiting period, I put out this solution as a temp option till we have a better solution. This is a one-liner with deep cloning. This is horrible, I agree... But given the experimental context at that time, it is not that bad.
@LazarLjubenović of course in 2018 that is the case and I totally agree with you today, but in 2016 webpack didn't have tree shaking so you'd be importing an entire library in most cases.
|
36

The alternative for deep copying objects having nested objects inside is by using lodash's cloneDeep method.

For Angular, you can do it like this:

Install lodash with yarn add lodash or npm install lodash.

In your component, import cloneDeep and use it:

import { cloneDeep } from "lodash";
...
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.

2 Comments

"only 18kb" added to the output to just be able to deep copy objects? JavaScript is a mess.
After reading your referenced article, I understanf the cloneDeep method instantiates a new object. Should we still use it if we already have a destination object ?
20

For shallow copying you could use Object.assign which is a ES6 feature

let x = { name: 'Marek', age: 20 };
let y = Object.assign({}, x);
x === y; //false

DO NOT use it for deep cloning

2 Comments

What can be used for deep cloning?
Why give this answer if it doesn't answer the question?! This answer is irrelevant.
15

Use lodash as bertandg indicated. The reason angular no longer has this method, is because angular 1 was a stand-alone framework, and external libraries often ran into issues with the angular execution context. Angular 2 does not have that problem, so use whatever library that you want.

https://lodash.com/docs#cloneDeep

Comments

10

The simplest solution I've found is:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

*IMPORTANT STEPS: You must install lodash to use this (which was unclear from other answers):

$ npm install --save lodash

$ npm install --save @types/lodash

and then import it in your ts file:

import * as _ from "lodash";

1 Comment

This creates a warning in Angular 10: WARNING in /.../test.component.ts depends on 'lodash'. CommonJS or AMD dependencies can cause optimization bailouts.
9

If you want to copy a class instance, you can use Object.assign too, but you need to pass a new instance as a first parameter (instead of {}) :

class MyClass {
    public prop1: number;
    public prop2: number;

    public summonUnicorn(): void {
        alert('Unicorn !');
    }
}

let instance = new MyClass();
instance.prop1 = 12;
instance.prop2 = 42;

let wrongCopy = Object.assign({}, instance);
console.log(wrongCopy.prop1); // 12
console.log(wrongCopy.prop2); // 42
wrongCopy.summonUnicorn() // ERROR : undefined is not a function

let goodCopy = Object.assign(new MyClass(), instance);
console.log(goodCopy.prop1); // 12
console.log(goodCopy.prop2); // 42
goodCopy.summonUnicorn() // It works !

1 Comment

this is ectually what I need
7

As others have already pointed out, using lodash or underscore is probably the best solution. But if you do not need those libraries for anything else, you can probably use something like this:

  function deepClone(obj) {

    // return value is input is not an Object or Array.
    if (typeof(obj) !== 'object' || obj === null) {
      return obj;    
    }

    let clone;

    if(Array.isArray(obj)) {
      clone = obj.slice();  // unlink Array reference.
    } else {
      clone = Object.assign({}, obj); // Unlink Object reference.
    }

    let keys = Object.keys(clone);

    for (let i=0; i<keys.length; i++) {
      clone[keys[i]] = deepClone(clone[keys[i]]); // recursively unlink reference to nested objects.
    }

    return clone; // return unlinked clone.

  }

That's what we decided to do.

2 Comments

// to unlink dates we can add: if(Object.prototype.toString.call(obj) === '[object Date]') { return new Date(obj.getTime()); }
or check date using instance type - if(obj instanceof Date){return new Date(obj.getTime())}
4
let newObj = JSON.parse(JSON.stringify(obj))

The JSON.stringify() method converts a JavaScript object or value to a JSON string

2 Comments

This has already been said exhaustively above that this is the worst way to treat it!
@Alessandro: Care to explain your statement? Original question is not for any particular scenario. This can be used for many cases and may not be the best for some. This is just one of many ways to do it. Explain how this is the worst?
2

You can clone the Array like

 this.assignCustomerList = Object.assign([], this.customerList);

And clone the object like

this.assignCustomer = Object.assign({}, this.customer);

1 Comment

This is wrong since angularjs.copy supports deepCopy. Hence your answer is misleading since doesn't support deepClone of an object.
2

I have created a service to use with Angular 5 or higher, it uses the angular.copy() the base of angularjs, it works well for me. Additionally, there are other functions like isUndefined, etc. I hope it helps. Like any optimization, it would be nice to know. regards

import { Injectable } from '@angular/core';

@Injectable({providedIn: 'root'})
export class AngularService {

  private TYPED_ARRAY_REGEXP = /^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/;
  private stackSource = [];
  private stackDest = [];

  constructor() { }

  public isNumber(value: any): boolean {
    if ( typeof value === 'number' ) { return true; }
    else { return false; }
  }

  public isTypedArray(value: any) {
    return value && this.isNumber(value.length) && this.TYPED_ARRAY_REGEXP.test(toString.call(value));
  }

  public isArrayBuffer(obj: any) {
    return toString.call(obj) === '[object ArrayBuffer]';
  }

  public isUndefined(value: any) {return typeof value === 'undefined'; }

  public isObject(value: any) {  return value !== null && typeof value === 'object'; }

  public isBlankObject(value: any) {
    return value !== null && typeof value === 'object' && !Object.getPrototypeOf(value);
  }

  public isFunction(value: any) { return typeof value === 'function'; }

  public setHashKey(obj: any, h: any) {
    if (h) { obj.$$hashKey = h; }
    else { delete obj.$$hashKey; }
  }

  private isWindow(obj: any) { return obj && obj.window === obj; }

  private isScope(obj: any) { return obj && obj.$evalAsync && obj.$watch; }


  private copyRecurse(source: any, destination: any) {

    const h = destination.$$hashKey;

    if (Array.isArray(source)) {
      for (let i = 0, ii = source.length; i < ii; i++) {
        destination.push(this.copyElement(source[i]));
      }
    } else if (this.isBlankObject(source)) {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    } else if (source && typeof source.hasOwnProperty === 'function') {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    } else {
      for (const key of Object.keys(source)) {
        destination[key] = this.copyElement(source[key]);
      }
    }
    this.setHashKey(destination, h);
    return destination;
  }

  private copyElement(source: any) {

    if (!this.isObject(source)) {
      return source;
    }

    const index = this.stackSource.indexOf(source);

    if (index !== -1) {
      return this.stackDest[index];
    }

    if (this.isWindow(source) || this.isScope(source)) {
      throw console.log('Cant copy! Making copies of Window or Scope instances is not supported.');
    }

    let needsRecurse = false;
    let destination = this.copyType(source);

    if (destination === undefined) {
      destination = Array.isArray(source) ? [] : Object.create(Object.getPrototypeOf(source));
      needsRecurse = true;
    }

    this.stackSource.push(source);
    this.stackDest.push(destination);

    return needsRecurse
      ? this.copyRecurse(source, destination)
      : destination;
  }

  private copyType = (source: any) => {

    switch (toString.call(source)) {
      case '[object Int8Array]':
      case '[object Int16Array]':
      case '[object Int32Array]':
      case '[object Float32Array]':
      case '[object Float64Array]':
      case '[object Uint8Array]':
      case '[object Uint8ClampedArray]':
      case '[object Uint16Array]':
      case '[object Uint32Array]':
        return new source.constructor(this.copyElement(source.buffer), source.byteOffset, source.length);

      case '[object ArrayBuffer]':
        if (!source.slice) {
          const copied = new ArrayBuffer(source.byteLength);
          new Uint8Array(copied).set(new Uint8Array(source));
          return copied;
        }
        return source.slice(0);

      case '[object Boolean]':
      case '[object Number]':
      case '[object String]':
      case '[object Date]':
        return new source.constructor(source.valueOf());

      case '[object RegExp]':
        const re = new RegExp(source.source, source.toString().match(/[^\/]*$/)[0]);
        re.lastIndex = source.lastIndex;
        return re;

      case '[object Blob]':
        return new source.constructor([source], {type: source.type});
    }

    if (this.isFunction(source.cloneNode)) {
      return source.cloneNode(true);
    }
  }

  public copy(source: any, destination?: any) {

    if (destination) {
      if (this.isTypedArray(destination) || this.isArrayBuffer(destination)) {
        throw console.log('Cant copy! TypedArray destination cannot be mutated.');
      }
      if (source === destination) {
        throw console.log('Cant copy! Source and destination are identical.');
      }

      if (Array.isArray(destination)) {
        destination.length = 0;
      } else {
        destination.forEach((value: any, key: any) => {
          if (key !== '$$hashKey') {
            delete destination[key];
          }
        });
      }

      this.stackSource.push(source);
      this.stackDest.push(destination);
      return this.copyRecurse(source, destination);
    }

    return this.copyElement(source);
  }
}

Comments

1

I needed this feature just form my app 'models' (raw backend data converted to objects). So I ended up using a combination of Object.create (create new object from specified prototype) and Object.assign (copy properties between objects). Need to handle the deep copy manually. I created a gist for this.

Comments

1

If you are open to using an npm pacakage, you can try out "ngx-scv-util":

Install the package by running following command:

npm i ngx-scv-util --save

Import the package into your component using:

import { NgxScvUtil } from "ngx-scv-util";
....
constructor(private util: NgxScvUtil) {}

Usage:

let newObject = this.util.deepCopy(originalObject);

It is a very light package. More information is available here: https://www.npmjs.com/package/ngx-scv-util

Comments

0

Had the same Issue, and didn't wanna use any plugins just for deep cloning:

static deepClone(object): any {
        const cloneObj = (<any>object.constructor());
        const attributes = Object.keys(object);
        for (const attribute of attributes) {
            const property = object[attribute];

            if (typeof property === 'object') {
                cloneObj[attribute] = this.deepClone(property);
            } else {
                cloneObj[attribute] = property;
            }
        }
        return cloneObj;
    }

Credits: I made this function more readable, please check the exceptions to its functionality below

Comments

0

I as well as you faced a problem of work angular.copy and angular.expect because they do not copy the object or create the object without adding some dependencies. My solution was this:

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

Comments

0

If you are not already using lodash I wouldn't recommend installing it just for this one method. I suggest instead a more narrowly specialized library such as 'clone':

npm install clone

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.