Qual è l'alternativa a angular.copy in Angular


136

Come posso copiare un oggetto e perderne il riferimento in Angolare?

Con AngularJS, posso usare angular.copy(object), ma sto riscontrando qualche errore nell'usarlo in Angular.

ECCEZIONE: ReferenceError: angularnon definito


Controlla questa soluzione che potrebbe aiutare: Link
Nourdine Alouane

In molte situazioni, potresti voler usare .copy()ma in realtà non ne avresti bisogno. In vari progetti AngJS1 che ho visto, è stato un lavoro eccessivo, in cui una copia manuale delle relative sottostrutture avrebbe creato un codice più pulito. Forse faceva parte della decisione di non attuarlo da parte della squadra angolare.
phil294,

tra l'altro, legati (e anche senza risposta): stackoverflow.com/questions/41124528/...
phil294

Risposte:


180

Supponendo che si stia utilizzando ES6, è possibile utilizzare var copy = Object.assign({}, original). Funziona con browser moderni; se hai bisogno di supportare i browser più vecchi controlla questo polyfill

aggiornare:

Con TypeScript 2.1+, è disponibile la notazione di diffusione degli oggetti abbreviati ES6:

const copy = { ...original }

75
Si noti che angular.copy()crea una copia profonda contrariamente a Object.assign(). Se si desidera una copia approfondita, utilizzare lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

in Webstorm ho ottenuto Unresolved function or method assign(); Dettagli IDE: Webstorm 2016.2. Come posso risolverlo?
mihai,

2
@meorfi Vai a File -> Settings -> Languages & Frameworks -> Javascripte imposta Javascript language versionsu ECMAScript 6.0.
Siri0S,

@bertrandg _.clone (valore) è diverso da quello angular.copy (), non creerà nuova istanza, in modo da _.cloneDeep (valore) ancora creare un riferimento stackoverflow.com/questions/26411754/...
Zealitude

5
Inoltre, se stai copiando un array, usa:const copy = [ ...original ]
daleyjem il

43

Fino a quando non avremo una soluzione migliore, è possibile utilizzare quanto segue:

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

EDIT: chiarimento

Nota: la soluzione di cui sopra doveva essere solo un liner a correzione rapida, fornito in un momento in cui Angular 2 era in fase di sviluppo attivo. La mia speranza era che alla fine avremmo potuto ottenere un equivalente di angular.copy(). Pertanto non volevo scrivere o importare una libreria di clonazione profonda.

Questo metodo presenta anche problemi con l'analisi delle proprietà della data (diventerà una stringa).

Non utilizzare questo metodo nelle app di produzione . Usalo solo nei tuoi progetti sperimentali - quelli che stai facendo per imparare Angular 2.


11
questo rovina le tue date ed è lento come l'inferno.
LanderV,

5
Non è lento come importare un'intera libreria per eseguire una singola attività, purché ciò che stai facendo sia piuttosto semplice ...
Ian Belcher

1
è orribile, non usarlo mai
Murhaf Sousli,

1
@MurhafSousli, per favore, prova a capire il contesto di questa risposta. Questo è stato fornito quando Angular 2 era in fase di sviluppo e la speranza era che alla fine avessimo un equivalente della funzione angular.copy (). Per colmare il periodo di attesa, ho messo questa soluzione come opzione temporanea fino a quando non avremo una soluzione migliore. Questa è una linea con clonazione profonda. È orribile , sono d'accordo ... Ma dato il contesto sperimentale in quel momento, non è poi così male.
Mani,

1
@ LazarLjubenović ovviamente nel 2018 è il caso e sono totalmente d'accordo con te oggi , ma nel 2016 il webpack non ha fatto tremare gli alberi, quindi nella maggior parte dei casi dovresti importare un'intera libreria.
Ian Belcher,

22

L'alternativa per la copia in profondità di oggetti con oggetti nidificati all'interno è l'utilizzo del metodo cloneDeep di lodash.

Per Angular, puoi farlo in questo modo:

Installa lodash con yarn add lodasho npm install lodash.

Nel tuo componente, importalo cloneDeepe usalo:

import { cloneDeep } from "lodash";
...
clonedObject = cloneDeep(originalObject);

Sono solo 18kb aggiunti alla tua build, vale la pena per i vantaggi.

Ho anche scritto un articolo qui , se hai bisogno di maggiori informazioni sul perché usare cloneDeep di lodash.


2
"solo 18kb" aggiunto all'output per poter solo copiare in profondità gli oggetti? JavaScript è un casino.
Endrju,

Dopo aver letto l'articolo di riferimento, ho capito che il cloneDeepmetodo crea un'istanza di un nuovo oggetto. Dovremmo ancora usarlo se abbiamo già un oggetto di destinazione?
Stephane

17

Per la copia superficiale è possibile utilizzare Object.assign che è una funzione ES6

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

NON usarlo per la clonazione profonda


3
Cosa può essere usato per la clonazione profonda?
DB

15

Usa lodash come indicato da bertandg. La ragione per cui angular non ha più questo metodo, è perché angular 1 era un framework autonomo e le librerie esterne spesso incontravano problemi con il contesto dell'esecuzione angolare. Angular 2 non ha questo problema, quindi usa qualunque libreria tu voglia.

https://lodash.com/docs#cloneDeep


8

Se vuoi copiare un'istanza di classe, puoi usare anche Object.assign, ma devi passare una nuova istanza come primo parametro (invece di {}):

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 !

8

La soluzione più semplice che ho trovato è:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* PASSAGGI IMPORTANTI: è necessario installare lodash per utilizzare questo (che non era chiaro da altre risposte):

$ npm install --save lodash

$ npm install --save @types/lodash

e poi importalo nel tuo file ts:

import * as _ from "lodash";

7

Come altri hanno già sottolineato, l'uso di lodash o underscore è probabilmente la soluzione migliore. Ma se non hai bisogno di quelle librerie per nient'altro, probabilmente puoi usare qualcosa del genere:

  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.

  }

Questo è quello che abbiamo deciso di fare.


1
// per scollegare le date possiamo aggiungere: if (Object.prototype.toString.call (obj) === '[data oggetto]') {return new Date (obj.getTime ()); }
A_J

1
o controlla la data usando il tipo di istanza - if (obj instanceof Date) {return new Date (obj.getTime ())}
Anoop Isaac,

0

Avevo bisogno di questa funzione per formare i "modelli" della mia app (dati di backend non elaborati convertiti in oggetti). Così ho finito per usare una combinazione di Object.create (creare un nuovo oggetto dal prototipo specificato) e Object.assign (copiare le proprietà tra gli oggetti). È necessario gestire manualmente la copia profonda. Ho creato un'idea per questo.


0

Aveva lo stesso problema e non volevo usare nessun plugin solo per la clonazione profonda:

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

Riconoscimenti: ho reso questa funzione più leggibile , controlla le eccezioni alla sua funzionalità di seguito


0

Ho creato un servizio da utilizzare con Angular 5 o versioni successive, utilizza la angular.copy ()base di angularjs, funziona bene per me. Inoltre ci sono altre funzioni come isUndefined, ecc. Spero che sia d'aiuto. Come ogni ottimizzazione, sarebbe bello saperlo. Saluti

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


0

Io e te abbiamo affrontato un problema di lavoro angular.copy e angular.expect perché non copiano l'oggetto o creano l'oggetto senza aggiungere alcune dipendenze. La mia soluzione era questa:

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

0
let newObj = JSON.parse(JSON.stringify(obj))

Il JSON.stringify()metodo converte un oggetto o valore JavaScript in una stringa JSON


2
Questo è già stato detto esaurientemente sopra che questo è il modo peggiore per trattarlo!
Alessandro

0

Puoi clonare l'array come

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

E clonare l'oggetto come

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

0

Se non stai già utilizzando lodash, non consiglierei di installarlo solo per questo metodo. Suggerisco invece una libreria più strettamente specializzata come 'clone':

npm install clone
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.