158

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

Lazar Ljubenović
  • 18,976
  • 10
  • 56
  • 91
Rodrigo Real
  • 2,993
  • 5
  • 14
  • 10
  • Check this solution it might help: [Link](http://stackoverflow.com/a/41475939/4509214) – Nourdine Alouane Jan 05 '17 at 01: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. – phil294 Feb 04 '17 at 21:47
  • by the way, related (and also unanswered): http://stackoverflow.com/questions/41124528/one-way-binding-objects-in-angular-2 – phil294 Feb 04 '17 at 21:48
  • Possible duplicate of [What is the most efficient way to deep clone an object in JavaScript?](https://stackoverflow.com/questions/122102/what-is-the-most-efficient-way-to-deep-clone-an-object-in-javascript) – Michael Freidgeim Oct 03 '19 at 07:24

16 Answers16

210

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 }
Sasxa
  • 40,334
  • 16
  • 88
  • 102
  • 96
    Note that `angular.copy()` creates a deep copy contrary to `Object.assign()`. If you want deep copy use lodash `_.cloneDeep(value)` https://lodash.com/docs#cloneDeep – bertrandg Apr 12 '16 at 15:02
  • in Webstorm I got `Unresolved function or method assign()` ; IDE Details: Webstorm 2016.2. How can I resolve that ? – mihai Sep 08 '16 at 14:10
  • 2
    @meorfi Go to `File -> Settings -> Languages & Frameworks -> Javascript` and set `Javascript language version` to `ECMAScript 6.0`. – Siri0S Sep 29 '16 at 13:24
  • @bertrandg _.clone(value) is different than angular.copy(), it will not create new instance, so as _.cloneDeep(value) it still create a reference https://stackoverflow.com/questions/26411754/should-i-use-angular-copy-or-clone – Zealitude Nov 30 '16 at 02:41
  • The Typescript solution does not work. Although you create a copy of the object, you als wrap an object around the copy. I think you should remove that part from your answer. – Michelangelo Dec 15 '16 at 16:15
  • @Michelangelo Not sure what you mean by `wrap object around copy`. They are the same - http://plnkr.co/edit/7jbdqqlFWdaSFLac9zhy?p=preview – Sasxa Dec 15 '16 at 20:03
  • @Sasxa Ok, you seem to be right but I got something like this: `{ { name: "Michelangelo" } }`...very odd. I am using Typescript from angular cli, will check my version. – Michelangelo Dec 15 '16 at 20:54
  • @Sasxa Hmmm, ok I use TypeScript 2.0.3 so not the latest, I am sorry about the confusion. – Michelangelo Dec 15 '16 at 21:03
  • 6
    Additionally, if you're copying an array, use: `const copy = [ ...original ]` – daleyjem Dec 14 '17 at 00:50
  • Neither {...object} or [...array] make any difference even when used together. Does this answer actually work? – lorless Mar 15 '19 at 12:31
  • WHY THIS answer shown almost at the bottom? It has most upvotes. **spread** operator works nice with nested properties as well. It is cool – canbax Dec 10 '19 at 10:03
  • Also note that if your original is `null` then `angular.copy(original)` will return `null` while this solution will return `{}`, which will behave differently if used in an `if` check or something. – crackmigg Mar 23 '21 at 14:59
  • ... spreading operator should not be used as an alternative to angular.copy, which is deep copy. You can probably use JSON.parse(JSON.stringify(xxx)) – Vincent Nov 09 '21 at 21:51
  • Object.assign is not an alternative for angular.copy,in fact, it only works for totally flat objects, otherwise it's probably always not what you are looking for. – Cesar Apr 03 '22 at 08:42
45

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.

Mani
  • 23,635
  • 6
  • 67
  • 54
  • 17
    this ruins your dates and is slow as hell. – LanderV May 18 '16 at 12:14
  • @LanderV Agreed, this is slow as hell. My objective, though not stated explicitly, was to have a single line solution. This allows us to replace it with the equivalent of angular.copy() when it becomes available later. – Mani Jun 02 '16 at 01:28
  • 9
    Not as slow as importing an entire library to do a single task though, as long as what you're doing is pretty simple... – Ian Belcher Aug 03 '16 at 06:49
  • 2
    this is horrible, never use that – Murhaf Sousli Oct 14 '16 at 14:00
  • 3
    @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. – Mani Oct 21 '16 at 07:20
  • @LeoCaseiro lodash.clonedeep is different thing. https://stackoverflow.com/questions/26411754/should-i-use-angular-copy-or-clone – Zealitude Nov 30 '16 at 02:43
  • @IanBelcher It _is_ slower than importing a library with a function. With ES6 module and tree-shaking, bundling, etc, you get just the function you use. – Lazar Ljubenović Sep 18 '18 at 13:37
  • 1
    @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. – Ian Belcher Sep 18 '18 at 16:12
  • Yikes I didn't even realize what year it was. Apologies for pinging! – Lazar Ljubenović Sep 18 '18 at 16:13
  • If the object what you would like to copy contains getters, those will be lost during use JSON.parse(JSON.stingify(obj)). – L. Kvri Mar 18 '19 at 12:27
37

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.

Tanja
  • 41
  • 2
  • 3
  • 11
BogdanC
  • 2,746
  • 2
  • 24
  • 22
  • 9
    "only 18kb" added to the output to just be able to deep copy objects? JavaScript is a mess. – Endrju Jan 02 '20 at 10:05
  • 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 ? – Stephane May 20 '20 at 12:01
19

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

mareks
  • 774
  • 6
  • 5
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

LanderV
  • 1,159
  • 12
  • 14
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";
Kyle Krzeski
  • 6,183
  • 6
  • 41
  • 52
  • This creates a warning in Angular 10: WARNING in /.../test.component.ts depends on 'lodash'. CommonJS or AMD dependencies can cause optimization bailouts. – Adrian Madaras Oct 20 '20 at 11:16
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 !
Guillaume Nury
  • 373
  • 3
  • 7
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.

Spitfire
  • 147
  • 1
  • 5
  • 1
    // to unlink dates we can add: if(Object.prototype.toString.call(obj) === '[object Date]') { return new Date(obj.getTime()); } – A_J Nov 07 '17 at 00:37
  • 1
    or check date using instance type - if(obj instanceof Date){return new Date(obj.getTime())} – Anoop Isaac Nov 24 '17 at 14:44
3
let newObj = JSON.parse(JSON.stringify(obj))

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

CharithJ
  • 46,289
  • 20
  • 116
  • 131
  • 3
    This has already been said exhaustively above that this is the worst way to treat it! – Alessandro Apr 09 '20 at 19:31
  • @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? – CharithJ Mar 25 '21 at 03:45
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);
  }
}
CodeChanger
  • 7,953
  • 5
  • 49
  • 80
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
    This is wrong since angularjs.copy supports deepCopy. Hence your answer is misleading since doesn't support deepClone of an object. – Rambou Sep 16 '20 at 11:10
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.

mppfiles
  • 2,397
  • 1
  • 21
  • 16
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

Sharath
  • 516
  • 1
  • 8
  • 19
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

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)
Viktor Ivliiev
  • 1,015
  • 4
  • 14
  • 21
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
Reuben Sivan
  • 157
  • 9