Quelle est l'alternative à angular.copy dans Angular


136

Comment copier un objet et perdre sa référence dans Angular?

Avec AngularJS, je peux utiliser angular.copy(object), mais j'obtiens une erreur en utilisant cela dans Angular.

EXCEPTION: ReferenceError: angularn'est pas défini


Vérifiez cette solution, cela pourrait aider: Lien
Nourdine Alouane

Dans de nombreuses situations, vous voudrez peut-être l'utiliser .copy()mais n'en auriez pas besoin. Dans divers projets AngJS1 que j'ai vus, c'était une exagération, où une copie manuelle des sous-structures pertinentes aurait permis un code plus propre. Peut-être que cela faisait partie de la décision de ne pas l'implémenter par l'équipe Angular.
phil294

en passant, lié (et également sans réponse): stackoverflow.com/questions/41124528/…
phil294

Réponses:


180

En supposant que vous utilisez ES6, vous pouvez utiliser var copy = Object.assign({}, original). Fonctionne dans les navigateurs modernes; si vous avez besoin de prendre en charge des navigateurs plus anciens, consultez ce polyfill

mettre à jour:

Avec TypeScript 2.1+, la notation d'étalement d'objet abrégée ES6 est disponible:

const copy = { ...original }

75
Notez que angular.copy()crée une copie profonde contrairement à Object.assign(). Si vous voulez une copie profonde, utilisez lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

dans Webstorm j'ai eu Unresolved function or method assign(); Détails de l'EDI: Webstorm 2016.2. Comment puis-je résoudre ça?
mihai

2
@meorfi Accédez à File -> Settings -> Languages & Frameworks -> Javascriptet réglez Javascript language versionsur ECMAScript 6.0.
Siri0S

@bertrandg _.clone (valeur) est différent de angular.copy (), il ne créera pas de nouvelle instance, donc comme _.cloneDeep (valeur), il créera toujours une référence stackoverflow.com/questions/26411754
...

5
De plus, si vous copiez un tableau, utilisez:const copy = [ ...original ]
daleyjem

43

Jusqu'à ce que nous ayons une meilleure solution, vous pouvez utiliser les éléments suivants:

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

EDIT: Clarification

Remarque: la solution ci-dessus était uniquement destinée à être une solution rapide à une seule doublure, fournie à un moment où Angular 2 était en développement actif. J'espérais que nous pourrions éventuellement obtenir un équivalent de angular.copy(). Par conséquent, je ne voulais pas écrire ou importer une bibliothèque de clonage profond.

Cette méthode a également des problèmes avec l'analyse des propriétés de date (elle deviendra une chaîne).

Veuillez ne pas utiliser cette méthode dans les applications de production . Utilisez-le uniquement dans vos projets expérimentaux - ceux que vous faites pour apprendre Angular 2.


11
cela ruine vos rendez-vous et est lent comme l'enfer.
LanderV

5
Pas aussi lent que d'importer une bibliothèque entière pour faire une seule tâche, tant que ce que vous faites est assez simple ...
Ian Belcher

1
c'est horrible, ne jamais utiliser ça
Murhaf Sousli

1
@MurhafSousli veuillez essayer de comprendre le contexte de cette réponse. Cela a été fourni lorsque Angular 2 était en cours de développement, et l'espoir était que nous obtiendrons éventuellement un équivalent de la fonction angular.copy (). Pour combler la période d'attente, j'ai proposé cette solution comme option temporaire jusqu'à ce que nous ayons une meilleure solution. Il s'agit d'un one-liner avec un clonage profond. C'est horrible , je suis d'accord ... Mais étant donné le contexte expérimental de l'époque, ce n'est pas si mal.
Mani

1
@ LazarLjubenović bien sûr en 2018, c'est le cas et je suis tout à fait d'accord avec vous aujourd'hui , mais en 2016, le webpack n'avait pas d'arbre tremblant, vous importeriez donc une bibliothèque entière dans la plupart des cas.
Ian Belcher

22

L'alternative pour copier en profondeur des objets ayant des objets imbriqués à l'intérieur consiste à utiliser la méthode cloneDeep de lodash.

Pour Angular, vous pouvez le faire comme ceci:

Installez lodash avec yarn add lodashou npm install lodash.

Dans votre composant, importez-le cloneDeepet utilisez-le:

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

Il ne s'agit que de 18 Ko ajoutés à votre build, ce qui en vaut la peine.

J'ai également écrit un article ici , si vous avez besoin de plus d'informations sur l'utilisation de cloneDeep de lodash.


2
"seulement 18 ko" ajouté à la sortie pour pouvoir simplement copier des objets en profondeur? JavaScript est un gâchis.
Endrju le

Après avoir lu votre article référencé, je comprends que la cloneDeepméthode instancie un nouvel objet. Devrions-nous continuer à l'utiliser si nous avons déjà un objet de destination?
Stéphane

17

Pour la copie superficielle, vous pouvez utiliser Object.assign qui est une fonctionnalité ES6

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

NE PAS l'utiliser pour le clonage profond


3
Que peut-on utiliser pour le clonage profond?
DB

15

Utilisez le lodash comme indiqué par bertandg. La raison pour laquelle angular n'a plus cette méthode est que angular 1 était un framework autonome et que les bibliothèques externes rencontraient souvent des problèmes avec le contexte d'exécution angulaire. Angular 2 n'a pas ce problème, alors utilisez la bibliothèque de votre choix.

https://lodash.com/docs#cloneDeep


8

Si vous souhaitez copier une instance de classe, vous pouvez également utiliser Object.assign, mais vous devez passer une nouvelle instance comme premier paramètre (au lieu 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

La solution la plus simple que j'ai trouvée est:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* ÉTAPES IMPORTANTES: Vous devez installer lodash pour utiliser ceci (ce qui n'était pas clair d'après les autres réponses):

$ npm install --save lodash

$ npm install --save @types/lodash

puis importez-le dans votre fichier ts:

import * as _ from "lodash";

7

Comme d'autres l'ont déjà souligné, l'utilisation de lodash ou de soulignement est probablement la meilleure solution. Mais si vous n'avez pas besoin de ces bibliothèques pour autre chose, vous pouvez probablement utiliser quelque chose comme ceci:

  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.

  }

C'est ce que nous avons décidé de faire.


1
// pour dissocier les dates, nous pouvons ajouter: if (Object.prototype.toString.call (obj) === '[object Date]') {return new Date (obj.getTime ()); }
A_J

1
ou vérifiez la date en utilisant le type d'instance - if (obj instanceof Date) {return new Date (obj.getTime ())}
Anoop Isaac

0

J'avais besoin de cette fonctionnalité pour former simplement mes `` modèles '' d'application (données brutes de backend converties en objets). J'ai donc fini par utiliser une combinaison de Object.create (créer un nouvel objet à partir du prototype spécifié) et Object.assign (copier les propriétés entre les objets). Besoin de gérer la copie complète manuellement. J'ai créé l' essentiel pour cela.


0

J'ai eu le même problème et je ne voulais pas utiliser de plugins uniquement pour le clonage en profondeur:

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édits: j'ai rendu cette fonction plus lisible , veuillez vérifier les exceptions à sa fonctionnalité ci-dessous


0

J'ai créé un service à utiliser avec Angular 5 ou supérieur, il utilise la angular.copy ()base d'angularjs, cela fonctionne bien pour moi. De plus, il existe d'autres fonctions comme isUndefined, etc. J'espère que cela aide. Comme toute optimisation, ce serait bien à savoir. Cordialement

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

Moi aussi que vous faisiez face à un problème de travail angular.copy et angular.expect car ils ne copient pas l'objet ou ne créent pas l'objet sans ajouter quelques dépendances. Ma solution était la suivante:

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

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

La JSON.stringify()méthode convertit un objet ou une valeur JavaScript en une chaîne JSON


2
Cela a déjà été dit de manière exhaustive ci-dessus que c'est la pire façon de la traiter!
Alessandro le

0

Vous pouvez cloner le tableau comme

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

Et cloner l'objet comme

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

0

Si vous n'utilisez pas déjà lodash, je ne recommanderais pas de l'installer uniquement pour cette méthode. Je suggère plutôt une bibliothèque plus étroitement spécialisée telle que 'clone':

npm install clone
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.