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


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 }

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.


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.


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


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


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 !

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

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ć.


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 .


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


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)

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

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


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

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

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

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.