O que é uma alternativa ao angular.copy no Angular


136

Como posso copiar um objeto e perder sua referência no Angular?

Com o AngularJS, posso usar angular.copy(object), mas estou recebendo algum erro ao usá-lo no Angular.

EXCEÇÃO: ReferenceError: angularnão está definido


Marque esta solução que pode ajudar: Link
Nourdine Alouane

Em muitas situações, você pode querer usar, .copy()mas na verdade não precisaria. Em vários projetos do AngJS1 que eu vi, foi um exagero, onde uma cópia manual das subestruturas relevantes teria criado um código mais limpo. Talvez isso tenha sido parte da decisão de não implementá-lo pela equipe Angular.
precisa saber é

a propósito, relacionado (e também sem resposta): stackoverflow.com/questions/41124528/…
phil294

Respostas:


180

Supondo que você esteja usando o ES6, você pode usá-lo var copy = Object.assign({}, original). Funciona em navegadores modernos; se você precisar oferecer suporte a navegadores antigos, confira este polyfill

atualizar:

Com o TypeScript 2.1+, a notação abreviada de objeto ES6 está disponível:

const copy = { ...original }

75
Observe que angular.copy()cria uma cópia profunda contrária a Object.assign(). Se você quiser profunda cópia utilização lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

no Webstorm eu consegui Unresolved function or method assign(); Detalhes do IDE: Tempestade na Web 2016.2. Como posso resolver isso?
mihai

2
@meorfi Vá para File -> Settings -> Languages & Frameworks -> Javascripte defina Javascript language versioncomo ECMAScript 6.0.
Siri0S

@bertrandg _.clone (value) é diferente de angular.copy (), ele não criará uma nova instância; portanto, como _.cloneDeep (value) ele ainda criará uma referência stackoverflow.com/questions/26411754/…
Zealitude

5
Além disso, se você estiver copiando uma matriz, use:const copy = [ ...original ]
daleyjem

43

Até termos uma solução melhor, você pode usar o seguinte:

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

EDIT: Esclarecimento

Observação: a solução acima foi projetada apenas para ser um liner de correção rápida, fornecido no momento em que o Angular 2 estava em desenvolvimento ativo. Minha esperança era que pudéssemos obter um equivalente angular.copy(). Portanto, eu não queria escrever ou importar uma biblioteca de clonagem profunda.

Este método também possui problemas com a análise das propriedades da data (ele se tornará uma sequência).

Por favor, não use esse método em aplicativos de produção . Use-o apenas em seus projetos experimentais - aqueles que você está fazendo para aprender o Angular 2.


11
isso estraga suas datas e é lento como o inferno.
LanderV

5
Não tão lento como a importação de uma biblioteca inteira para fazer uma única tarefa, porém, contanto que você está fazendo é muito simples ...
Ian Belcher

1
isto é horrível, nunca use que
Murhaf Sousli

1
@MurhafSousli, tente entender o contexto desta resposta. Isso foi fornecido quando o Angular 2 estava em desenvolvimento, e a esperança era que, eventualmente, obtivéssemos um equivalente à função angular.copy (). Para preencher o período de espera, coloquei essa solução como uma opção temporária até termos uma solução melhor. Este é um one-liner com clonagem profunda. Isso é horrível , eu concordo ... Mas, dado o contexto experimental da época, não é tão ruim assim.
Mani

1
@ LazarLjubenović, é claro que em 2018 é esse o caso e concordo totalmente com você hoje , mas em 2016 o webpack não tinha tremores de árvores, então você importaria uma biblioteca inteira na maioria dos casos.
Ian Belcher

22

A alternativa para copiar objetos com objetos aninhados é usar o método cloneDeep do lodash.

Para Angular, você pode fazer assim:

Instale o lodash com yarn add lodashou npm install lodash.

No seu componente, importe cloneDeepe use-o:

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

São apenas 18kb adicionados à sua compilação, vale a pena pelos benefícios.

Também escrevi um artigo aqui , se você precisar de mais informações sobre o porquê do uso do cloneDeep do lodash.


2
"apenas 18kb" adicionado à saída para poder copiar objetos em profundidade? JavaScript está uma bagunça.
Endrju

Depois de ler o artigo mencionado, entendi que o cloneDeepmétodo instancia um novo objeto. Ainda devemos usá-lo se já tivermos um objeto de destino?
Stephane

17

Para cópia superficial, você pode usar Object.assign, que é um recurso do ES6

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

NÃO o use para clonagem profunda


3
O que pode ser usado para clonagem profunda?
DB

15

Use lodash como bertandg indicado. O motivo pelo qual o angular não possui mais esse método é porque o angular 1 era uma estrutura independente e as bibliotecas externas frequentemente enfrentavam problemas com o contexto de execução angular. O Angular 2 não tem esse problema; portanto, use a biblioteca que você deseja.

https://lodash.com/docs#cloneDeep


8

Se você deseja copiar uma instância de classe, também pode usar Object.assign, mas precisa passar uma nova instância como primeiro parâmetro (em vez de {}):

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

A solução mais simples que encontrei é:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* ETAPAS IMPORTANTES: Você deve instalar o lodash para usá-lo (o que não é claro em outras respostas):

$ npm install --save lodash

$ npm install --save @types/lodash

e importe-o no seu arquivo ts:

import * as _ from "lodash";

7

Como outros já apontaram, usar lodash ou sublinhado é provavelmente a melhor solução. Mas se você não precisar dessas bibliotecas para mais nada, provavelmente poderá usar algo como isto:

  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.

  }

Foi o que decidimos fazer.


1
// para desvincular datas, podemos adicionar: if (Object.prototype.toString.call (obj) === '[data do objeto]') {return new Date (obj.getTime ()); }
A_J 7/11

1
ou verifique a data usando o tipo de instância - if (obj instanceof Date) {return new Date (obj.getTime ())}
Anoop Isaac

0

Eu precisava desse recurso apenas para formar 'modelos' do meu aplicativo (dados de back-end brutos convertidos em objetos). Então, acabei usando uma combinação de Object.create (criar novo objeto a partir do protótipo especificado) e Object.assign (copiar propriedades entre objetos). Precisa lidar com a cópia profunda manualmente. Eu criei uma essência para isso.


0

Tinha o mesmo problema e não queria usar nenhum plug-in apenas para clonagem profunda:

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

Créditos: Tornei esta função mais legível . Verifique as exceções à sua funcionalidade abaixo


0

Eu criei um serviço para usar com o Angular 5 ou superior, ele usa a angular.copy ()base do angularjs, funciona bem para mim. Além disso, existem outras funções como isUndefined, etc. Espero que ajude. Como qualquer otimização, seria bom saber. Saudações

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

Eu e você também enfrentamos um problema do trabalho angular.copy e angular.expect porque eles não copiam o objeto ou criam o objeto sem adicionar algumas dependências. Minha solução foi esta:

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

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

O JSON.stringify()método converte um objeto ou valor JavaScript em uma sequência JSON


2
Já foi dito exaustivamente acima que esta é a pior maneira de tratá-lo!
Alessandro

0

Você pode clonar a matriz como

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

E clonar o objeto como

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

0

Se você ainda não estiver usando o lodash, eu não recomendaria instalá-lo apenas para este método. Sugiro uma biblioteca mais restrita, como o 'clone':

npm install clone
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.