Reputation: 3043
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
Upvotes: 160
Views: 175732
Reputation: 606
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
Upvotes: 1
Reputation: 21
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);
}
}
Upvotes: 2
Reputation: 177
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
Upvotes: 0
Reputation: 89
You can clone the Array like
this.assignCustomerList = Object.assign([], this.customerList);
And clone the object like
this.assignCustomer = Object.assign({}, this.customer);
Upvotes: 2
Reputation: 2784
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.
Upvotes: 36
Reputation: 47540
let newObj = JSON.parse(JSON.stringify(obj))
The JSON.stringify()
method converts a JavaScript object or value to a JSON string
Upvotes: 4
Reputation: 1334
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)
Upvotes: 0
Reputation: 1
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
Upvotes: 0
Reputation: 2417
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.
Upvotes: 1
Reputation: 6527
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";
Upvotes: 10
Reputation: 157
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.
Upvotes: 7
Reputation: 393
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 !
Upvotes: 9
Reputation: 784
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
Upvotes: 20
Reputation: 41274
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 }
Upvotes: 212
Reputation: 24265
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.
Upvotes: 47
Reputation: 1177
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
Upvotes: 15