Co jest alternatywą dla angular.copy w Angular

138

Jak mogę skopiować obiekt i stracić jego odniesienie w Angular?

Z AngularJS mogę używać angular.copy(object), ale otrzymuję błąd podczas używania tego w Angular.

WYJĄTEK: ReferenceError: angularnie jest zdefiniowany

Rodrigo Real
źródło
Sprawdź to rozwiązanie, które może pomóc: Link
Nourdine Alouane
W wielu sytuacjach możesz chcieć użyć, .copy()ale w rzeczywistości nie potrzebujesz go. W różnych projektach AngJS1, które widziałem, była to przesada, w której ręczna kopia odpowiednich podstruktur byłaby zrobiona dla czystszego kodu. Może to była część decyzji o nie wdrożeniu go przez zespół Angular.
phil294
nawiasem mówiąc, powiązane (i również bez odpowiedzi): stackoverflow.com/questions/41124528/…
phil294

Odpowiedzi:

181

Zakładając, że używasz ES6, możesz użyć var copy = Object.assign({}, original). Działa w nowoczesnych przeglądarkach; jeśli potrzebujesz obsługi starszych przeglądarek, sprawdź ten polyfill

aktualizacja:

W przypadku języka TypeScript 2.1+ dostępna jest skrócona notacja rozprzestrzeniania obiektów w ES6:

const copy = { ...original }
Sasxa
źródło
78
Zauważ, że angular.copy()tworzy głęboką kopię w przeciwieństwie do Object.assign(). Jeśli chcesz głęboką kopię, użyj lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg
w Webstorm dostałem Unresolved function or method assign(); Szczegóły IDE: Webstorm 2016.2. Jak mogę to rozwiązać?
mihai,
2
@meorfi Idź do File -> Settings -> Languages & Frameworks -> Javascripti ustaw Javascript language versionna ECMAScript 6.0.
Siri0S,
@bertrandg _.clone (value) różni się od angular.copy (), nie utworzy nowej instancji, więc jako _.cloneDeep (wartość) nadal tworzy referencję stackoverflow.com/questions/26411754/ ...
Zealitude
5
Dodatkowo, jeśli kopiujesz tablicę, użyj:const copy = [ ...original ]
daleyjem
43

Dopóki nie mamy lepszego rozwiązania, możesz skorzystać z:

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

EDYCJA: wyjaśnienie

Uwaga: powyższe rozwiązanie miało być tylko jednym szybkim rozwiązaniem, dostarczonym w czasie, gdy Angular 2 był aktywnie rozwijany. Miałem nadzieję, że w końcu uda nam się uzyskać odpowiednik angular.copy(). Dlatego nie chciałem pisać ani importować biblioteki do głębokiego klonowania.

Ta metoda ma również problemy z analizowaniem właściwości daty (stanie się ciągiem znaków).

Nie używaj tej metody w aplikacjach produkcyjnych . Używaj go tylko w swoich eksperymentalnych projektach - tych, które robisz do nauki Angulara 2.

Mani
źródło
12
to psuje twoje randki i jest powolne jak diabli.
LanderV
6
Nie tak powolne, jak importowanie całej biblioteki w celu wykonania jednego zadania, o ile to, co robisz, jest dość proste ...
Ian Belcher
1
to jest okropne, nigdy tego nie używaj
Murhaf Sousli
2
@MurhafSousli, spróbuj zrozumieć kontekst tej odpowiedzi. Zostało to zapewnione, gdy Angular 2 był w fazie rozwoju, i mieliśmy nadzieję, że ostatecznie otrzymamy odpowiednik funkcji angular.copy (). Aby skrócić okres oczekiwania, przedstawiłem to rozwiązanie jako opcję tymczasową, dopóki nie będziemy mieli lepszego rozwiązania. Jest to jednolinijkowy z głębokim klonowaniem. To okropne , zgadzam się ... Ale biorąc pod uwagę kontekst eksperymentalny w tamtym czasie, nie jest tak źle.
Mani
1
@ LazarLjubenović oczywiście tak jest w 2018 roku i dzisiaj całkowicie się z tobą zgadzam , ale w 2016 webpack nie potrząsał drzewem, więc w większości przypadków importowałbyś całą bibliotekę.
Ian Belcher,
24

Alternatywą dla głębokiego kopiowania obiektów z zagnieżdżonymi obiektami jest użycie metody cloneDeep lodash.

W przypadku Angulara możesz to zrobić w następujący sposób:

Zainstaluj lodash za pomocą yarn add lodashlub npm install lodash.

W swoim komponencie zaimportuj go cloneDeepi użyj:

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

Do kompilacji zostało dodane tylko 18 KB, co jest warte korzyści.

Napisałem tu również artykuł , jeśli potrzebujesz więcej informacji na temat tego, dlaczego używasz cloneDeep lodash.

BogdanC
źródło
2
„tylko 18kb” dodane do wyniku, aby móc po prostu kopiować obiekty? JavaScript to bałagan.
Endrju
Po przeczytaniu artykułu, do którego się odwołujesz, zrozumiałem, że cloneDeepmetoda tworzy instancję nowego obiektu. Czy nadal powinniśmy go używać, jeśli mamy już obiekt docelowy?
Stephane
18

Do płytkiego kopiowania możesz użyć Object. assign, który jest funkcją ES6

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

NIE używaj go do głębokiego klonowania

klacze
źródło
3
Czego można użyć do głębokiego klonowania?
DB
15

Użyj lodash zgodnie ze wskazaniami bertandg. Powodem, dla którego angular nie ma już tej metody, jest to, że angular 1 był samodzielnym szkieletem, a biblioteki zewnętrzne często napotykały problemy z kontekstem wykonania kątowego. Angular 2 nie ma tego problemu, więc użyj dowolnej biblioteki.

https://lodash.com/docs#cloneDeep

LanderV
źródło
9

Jeśli chcesz skopiować instancję klasy, możesz również użyć Object. assign, ale musisz przekazać nową instancję jako pierwszy parametr (zamiast {}):

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 !
Guillaume Nury
źródło
właśnie tego potrzebuję
ASLIM
8

Najprostsze rozwiązanie, które znalazłem, to:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* WAŻNE KROKI: Musisz zainstalować lodash, aby użyć tego (co było niejasne z innych odpowiedzi):

$ npm install --save lodash

$ npm install --save @types/lodash

a następnie zaimportuj go do swojego pliku ts:

import * as _ from "lodash";
William Hampshire
źródło
Tworzy to ostrzeżenie w Angular 10: OSTRZEŻENIE w /.../test.component.ts zależy od „lodash”. Zależności CommonJS lub AMD mogą powodować bailouty optymalizacji.
Adrian Madaras
7

Jak już inni zauważyli, użycie lodash lub podkreślenia jest prawdopodobnie najlepszym rozwiązaniem. Ale jeśli nie potrzebujesz tych bibliotek do niczego innego, prawdopodobnie możesz użyć czegoś takiego:

  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.

  }

To właśnie postanowiliśmy zrobić.

Choleryk
źródło
1
// aby odłączyć daty, możemy dodać: if (Object.prototype.toString.call (obj) === '[object Date]') {return new Date (obj.getTime ()); }
A_J,
1
lub sprawdź datę przy użyciu typu instancji - if (obj instanceof Date) {return new Date (obj.getTime ())}
Anoop Isaac
0

Potrzebowałem tej funkcji po prostu z „modeli” mojej aplikacji (surowe dane zaplecza przekonwertowane na obiekty). Więc skończyło się za pomocą kombinacji Object.create (tworzenie nowego obiektu z określonego prototypu) oraz Object.assign (Kopiowanie właściwości między obiektami). Musisz ręcznie obsługiwać głęboką kopię. Stworzyłem do tego sedno .

mppfiles
źródło
0

Miałem ten sam problem i nie chciałem używać żadnych wtyczek tylko do głębokiego klonowania:

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

Kredyty: sprawiłem, że ta funkcja jest bardziej czytelna , sprawdź poniżej wyjątki od jej funkcjonalności

Luc Bucher
źródło
0

Ja, jak ty, stanęliśmy przed problemem pracy kątowej. Kopie i kątowe. Oczekujemy, ponieważ nie kopiują obiektu ani nie tworzą obiektu bez dodawania pewnych zależności. Moje rozwiązanie było takie:

  copyFactory = (() ->
    resource = ->
      resource.__super__.constructor.apply this, arguments
      return
    this.extendTo resource
    resource
  ).call(factory)
Vitya Ivliiev
źródło
0
let newObj = JSON.parse(JSON.stringify(obj))

JSON.stringify()Metoda konwertuje obiekt JavaScript lub wartość na ciąg JSON

CharithJ
źródło
2
To zostało już wyczerpująco powiedziane powyżej, że jest to najgorszy sposób leczenia!
Alessandro
0

Możesz sklonować Array jak

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

I sklonuj obiekt jak

this.assignCustomer = Object.assign({}, this.customer);
Padmanabhan Velu
źródło
Jest to błędne, ponieważ angularjs.copy obsługuje funkcję deepCopy. Dlatego twoja odpowiedź jest myląca, ponieważ nie obsługuje funkcji DeepClone obiektu.
Rambou
0

Jeśli jeszcze nie używasz lodash, nie polecałbym instalowania go tylko dla tej jednej metody. Proponuję zamiast tego bardziej wyspecjalizowaną bibliotekę, taką jak „clone”:

npm install clone
Reuben Sivan
źródło
0

Stworzyłem usługę do użytku z Angular 5 lub nowszym, korzysta angular.copy()z bazy angularjs, działa dobrze dla mnie. Dodatkowo są inne funkcje, takie jak isUndefineditp. Mam nadzieję, że to pomoże. Jak w przypadku każdej optymalizacji, dobrze byłoby wiedzieć. pozdrowienia

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

César Holmes
źródło