90

I have an array of objects that is an input. Lets call it content.

When trying to deep copy it, it still has a reference to the previous array.

I need to duplicate that input array, and change one property of the duplicated part.

So long I've tried different methods that weren't successful.

ES6 way:

public duplicateArray() {
  arr = [...this.content]
  arr.map((x) => {x.status = DEFAULT});
  return this.content.concat(arr);
}

The slice way:

public duplicateArray() {
  arr = this.content.slice(0);
  arr.map((x) => {x.status = DEFAULT});
  return this.content.concat(arr);
}

In both of them all the objects inside the array have status: 'Default'.

What's the best approach to deep copy the array in Angular 2?

Joel Almeida
  • 7,939
  • 5
  • 25
  • 51

10 Answers10

143

Check this:

  let cloned = source.map(x => Object.assign({}, x));
YD1m
  • 5,845
  • 2
  • 19
  • 23
  • 8
    if i'm not missing something this doesn't work for strings-- when I try `var source = ["one","two","three"]; var cloned = source.map(x => Object.assign({},x));` I end up with cloned as: `[ { '0': 'o', '1': 'n', '2': 'e' }, { '0': 't', '1': 'w', '2': 'o' }, { '0': 't', '1': 'h', '2': 'r', '3': 'e', '4': 'e' } ]` – ossek Sep 12 '17 at 14:40
  • @ossec the Object.assign() method copies enumerable properties from a source object to a target object. String variable is an array-like object. Each character will be added as a new property to target object in cloned array. Your example works correctly and here need additional check for strings in source array. – YD1m Sep 13 '17 at 10:54
  • 8
    As for me `source.map(x => ({ ...x }));` suits the best. – Frankie Drake Mar 28 '18 at 12:09
  • 2
    Not compatible with internet explorer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign – agilob Jul 18 '18 at 07:11
  • 1
    If you want to clone the array object, This option is more preferable. – ruchit Oct 08 '18 at 09:53
  • 1
    After 2 hours of search. This worked! THANKS A TON! – Xonshiz Aug 06 '19 at 16:16
57

Simple:

let objCopy  = JSON.parse(JSON.stringify(obj));

This Also Works (Only for Arrays)

let objCopy2 = obj.slice()
Cameron Gilbert
  • 884
  • 8
  • 23
17

This is Daria's suggestion (see comment on the question) which works starting from TypeScript 2.1 and basically clones each element from the array:

this.clonedArray = theArray.map(e => ({ ... e }));
Alexei - check Codidact
  • 22,016
  • 16
  • 145
  • 164
16

This is working for me:

this.listCopy = Object.assign([], this.list);
kabus
  • 899
  • 1
  • 11
  • 20
  • @makkasi That's not true. changing this.list after this.listCopy has been set, will not affect this.listCopy for sure!! – kabus May 13 '18 at 10:16
  • 3
    OK.May be I'm wrong. I tested with this code and it changes the other list. May be the reason is somewhere else. I don't have access to computer at the moment. Will try this later. I deleted my previous comment. – makkasi May 13 '18 at 10:21
  • @kabus, it will if `this.list` contains objects. If you modify any object contained in `this.list`, the changes will be reflected to `this.listCopy`, because it keeps just references. – el.atomo Jun 12 '18 at 16:01
  • @el.atomo can you provide an example, because i cant reproduce it – kabus Jun 13 '18 at 04:35
  • 1
    Sure @kabus, ```let list = [{a: 1}]; let listCopy = Object.assign([], list); list[0].a = 2; console.log(list[0].a, listCopy[0].a);```. Sorry for the ugly formatting :) – el.atomo Jun 13 '18 at 08:17
  • @el.atomo and makkasi lol you guys are right, it's not working anymore. I swear this worked before! :) – kabus Jun 14 '18 at 13:28
  • @kabus, it does work if the array contains primitives, but it doesn't for objects: `let list = [1, 2, 3]; let listCopy = Object.assign([], list); list[0] = null; console.log(list[0], listCopy[0]);`, for that you need to implement/use a deep copy or use the JSON trick (if there're no circular references). – el.atomo Jun 14 '18 at 15:26
  • Not compatible with internet explorer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign – agilob Jul 18 '18 at 07:11
  • Linter fixed this to `this.listCopy = { ...[], ...this.list }` for me, so I guess it is also ok. – Antti Tanskanen Sep 28 '21 at 07:44
13

The only solution I've found (almost instantly after posting the question), is to loop through the array and use Object.assign()

Like this:

public duplicateArray() {
  let arr = [];
  this.content.forEach((x) => {
    arr.push(Object.assign({}, x));
  })
  arr.map((x) => {x.status = DEFAULT});
  return this.content.concat(arr);
}

I know this is not optimal. And I wonder if there's any better solutions.

Joel Almeida
  • 7,939
  • 5
  • 25
  • 51
  • Not compatible with internet explorer https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign – agilob Jul 18 '18 at 07:11
10

A clean way of 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 * 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
    Be aware, in case your originalObject is an array of objects, objects won't be deep copied, their reference will be copied. – Slaven Tomac Aug 26 '19 at 07:47
9

I found deep copy method in angular devkit, It's very normal, so... maybe you can just implement yourself or use that.

I prefer to use loadash, there a lot of objects and array operation methods that can use.

import { deepCopy } from '@angular-devkit/core/src/utils/object';

export class AppComponent {
  source = {
    ....
  }
  constructor() {
     const newObject = deepCopy(this.source);
  }
}
Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.1000.8
@angular-devkit/build-angular     0.1000.8
@angular-devkit/build-optimizer   0.1000.8
@angular-devkit/build-webpack     0.1000.8
@angular-devkit/core              10.0.8
@angular-devkit/schematics        10.0.8
@angular/cli                      10.0.8
@ngtools/webpack                  10.0.8
@schematics/angular               10.0.8
@schematics/update                0.1000.8
rxjs                              6.5.5
typescript                        3.9.7
webpack                           4.43.0
LiHao
  • 290
  • 3
  • 13
1

Here is my own. Doesn't work for complex cases, but for a simple array of Objects, it's good enough.

  deepClone(oldArray: Object[]) {
    let newArray: any = [];
    oldArray.forEach((item) => {
      newArray.push(Object.assign({}, item));
    });
    return newArray;
  }
Alex Beugnet
  • 4,003
  • 4
  • 23
  • 40
1

Alternatively, you can use the GitHub project ts-deepcopy, which is also available on npm, to clone your object, or just include the code snippet below.

/**
 * Deep copy function for TypeScript.
 * @param T Generic type of target/copied value.
 * @param target Target value to be copied.
 * @see Source project, ts-deepcopy https://github.com/ykdr2017/ts-deepcopy
 * @see Code pen https://codepen.io/erikvullings/pen/ejyBYg
 */
export const deepCopy = <T>(target: T): T => {
  if (target === null) {
    return target;
  }
  if (target instanceof Date) {
    return new Date(target.getTime()) as any;
  }
  if (target instanceof Array) {
    const cp = [] as any[];
    (target as any[]).forEach((v) => { cp.push(v); });
    return cp.map((n: any) => deepCopy<any>(n)) as any;
  }
  if (typeof target === 'object' && target !== {}) {
    const cp = { ...(target as { [key: string]: any }) } as { [key: string]: any };
    Object.keys(cp).forEach(k => {
      cp[k] = deepCopy<any>(cp[k]);
    });
    return cp as T;
  }
  return target;
};
Erik Vullings
  • 5,550
  • 2
  • 28
  • 23
-3

you can use use JQuery for deep copying :

var arr =[['abc'],['xyz']];
var newArr = $.extend(true, [], arr);
newArr.shift().shift();

console.log(arr); //arr still has [['abc'],['xyz']]