ทางเลือกอื่นสำหรับ angular.copy ใน Angular


138

ฉันจะคัดลอกวัตถุและสูญเสียการอ้างอิงใน Angular ได้อย่างไร

ด้วย AngularJS ฉันสามารถใช้ได้angular.copy(object)แต่ฉันได้รับข้อผิดพลาดในการใช้ Angular

EXCEPTION: ReferenceError: angularไม่ได้กำหนดไว้


ตรวจสอบวิธีนี้อาจช่วยได้: Link
Nourdine Alouane

ในหลาย ๆ สถานการณ์คุณอาจต้องการใช้.copy()แต่จริงๆแล้วไม่จำเป็นต้องใช้ ในโครงการ AngJS1 ต่างๆที่ฉันเคยเห็นมันเป็นการโอเวอร์คิลซึ่งจะมีการสร้างสำเนาโครงสร้างย่อยที่เกี่ยวข้องด้วยตนเองเพื่อรหัสที่สะอาดกว่า บางทีนั่นอาจเป็นส่วนหนึ่งของการตัดสินใจที่จะไม่ใช้งานโดยทีม Angular
phil294

โดยวิธีการที่เกี่ยวข้อง (และยังไม่ได้ตอบ): stackoverflow.com/questions/41124528/…
phil294

คำตอบ:


181

สมมติว่าคุณกำลังใช้ ES6 var copy = Object.assign({}, original)คุณสามารถใช้ ทำงานในเบราว์เซอร์ที่ทันสมัย หากคุณต้องการรองรับเบราว์เซอร์รุ่นเก่าลองดูpolyfillนี้

ปรับปรุง:

ด้วย TypeScript 2.1+ สามารถใช้สัญกรณ์การแพร่กระจายวัตถุชวเลข ES6 ได้:

const copy = { ...original }

78
โปรดทราบว่าการสร้างสำเนาขัดลึกangular.copy() Object.assign()หากคุณต้องการสำเนาลึกให้ใช้ lodash _.cloneDeep(value) lodash.com/docs#cloneDeep
bertrandg

ใน Webstorm ฉันได้รับUnresolved function or method assign(); รายละเอียด IDE: Webstorm 2016.2 ฉันจะแก้ไขได้อย่างไร
mihai

2
@meorfi ไปที่File -> Settings -> Languages & Frameworks -> Javascriptและตั้งค่าJavascript language versionเป็นECMAScript 6.0.
Siri0S

@bertrandg _.clone (value) แตกต่างจาก angular.copy () จะไม่สร้างอินสแตนซ์ใหม่ดังนั้นเนื่องจาก _.cloneDeep (value) จะยังคงสร้างstackoverflow.com/questions/26411754/…
Zealitude

5
นอกจากนี้หากคุณกำลังคัดลอกอาร์เรย์ให้ใช้:const copy = [ ...original ]
daleyjem

43

จนกว่าเราจะมีทางออกที่ดีกว่าคุณสามารถใช้สิ่งต่อไปนี้:

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

แก้ไข: ชี้แจง

โปรดทราบ: วิธีแก้ปัญหาข้างต้นมีไว้เพื่อแก้ไขปัญหาอย่างรวดเร็วหนึ่งซับซึ่งจัดทำขึ้นในช่วงเวลาที่ Angular 2 อยู่ระหว่างการพัฒนา angular.copy()ความหวังของฉันคือในที่สุดเราก็อาจได้รับเทียบเท่า ดังนั้นฉันจึงไม่ต้องการเขียนหรือนำเข้าไลบรารีการโคลนแบบเจาะลึก

วิธีนี้ยังมีปัญหากับคุณสมบัติวันที่แยกวิเคราะห์ (ซึ่งจะกลายเป็นสตริง)

โปรดอย่าใช้วิธีนี้ในแอปที่ใช้งานจริง ใช้เฉพาะในโครงการทดลองของคุณ - โครงการที่คุณกำลังทำเพื่อเรียนรู้ Angular 2


12
สิ่งนี้ทำลายวันที่ของคุณและช้าเหมือนนรก
LanderV

6
ไม่ช้าเหมือนการนำเข้าไลบรารีทั้งหมดเพื่อทำงานชิ้นเดียวตราบใดที่สิ่งที่คุณทำนั้นค่อนข้างง่าย ...
Ian Belcher

1
มันแย่มากไม่เคยใช้
Murhaf Sousli

2
@MurhafSousli โปรดพยายามเข้าใจบริบทของคำตอบนี้ สิ่งนี้มีให้เมื่อ Angular 2 อยู่ระหว่างการพัฒนาและความหวังก็คือในที่สุดเราจะได้ฟังก์ชัน angular.copy () เทียบเท่า เพื่อลดระยะเวลาการรอฉันจึงนำโซลูชันนี้เป็นตัวเลือกชั่วคราวจนกว่าเราจะมีทางออกที่ดีกว่า นี่คือซับเดียวที่มีการโคลนลึก นี่มันแย่มากฉันเห็นด้วย ... แต่จากบริบทการทดลองในตอนนั้นมันก็ไม่ได้แย่ขนาดนั้น
มานี

1
@ LazarLjubenovićแน่นอนในปี 2018 เป็นเช่นนั้นและวันนี้ฉันเห็นด้วยกับคุณโดยสิ้นเชิงแต่ในปี 2016 webpack ไม่มีต้นไม้สั่นดังนั้นคุณจะนำเข้าห้องสมุดทั้งหมดในกรณีส่วนใหญ่
Ian Belcher

24

อีกทางเลือกหนึ่งสำหรับการคัดลอกวัตถุแบบลึกที่มีวัตถุซ้อนอยู่ภายในคือการใช้วิธี cloneDeep ของ lodash

สำหรับ Angular คุณสามารถทำได้ดังนี้:

ติดตั้ง lodash ด้วยyarn add lodashหรือnpm install lodash.

ในส่วนประกอบของคุณนำเข้าcloneDeepและใช้งาน:

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

เพิ่มเข้าไปในงานสร้างของคุณเพียง 18kb คุ้มค่ากับผลประโยชน์

ฉันได้เขียนบทความไว้ที่นี่ด้วยหากคุณต้องการข้อมูลเชิงลึกเพิ่มเติมว่าเหตุใดจึงใช้ cloneDeep ของ lodash


2
"เพียง 18kb" เพิ่มลงในเอาต์พุตเพื่อให้สามารถคัดลอกวัตถุได้ลึก? JavaScript เป็นระเบียบ
Endrju

หลังจากอ่านบทความอ้างอิงของคุณฉันเข้าใจcloneDeepวิธีการสร้างอินสแตนซ์วัตถุใหม่ เราควรใช้มันหรือไม่ถ้าเรามีวัตถุปลายทางอยู่แล้ว?
Stephane

18

สำหรับการคัดลอกแบบตื้นคุณสามารถใช้ Object.assign ซึ่งเป็นคุณสมบัติ ES6

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

อย่าใช้สำหรับการโคลนลึก


3
สิ่งที่สามารถใช้ในการโคลนลึก?
DB

15

ใช้ lodash ตามที่ bertandg ระบุ เหตุผลที่เชิงมุมไม่มีวิธีนี้อีกต่อไปเนื่องจากเชิงมุม 1 เป็นเฟรมเวิร์กแบบสแตนด์อะโลนและไลบรารีภายนอกมักพบปัญหาเกี่ยวกับบริบทการดำเนินการเชิงมุม Angular 2 ไม่มีปัญหานั้นดังนั้นใช้ไลบรารีอะไรก็ได้ที่คุณต้องการ

https://lodash.com/docs#cloneDeep


9

หากคุณต้องการคัดลอกอินสแตนซ์คลาสคุณสามารถใช้ Object.assign ได้เช่นกัน แต่คุณต้องส่งอินสแตนซ์ใหม่เป็นพารามิเตอร์แรก (แทนที่จะเป็น {}):

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

วิธีแก้ปัญหาที่ง่ายที่สุดที่ฉันพบคือ:

let yourDeepCopiedObject = _.cloneDeep(yourOriginalObject);

* ขั้นตอนสำคัญ: คุณต้องติดตั้ง lodash เพื่อใช้สิ่งนี้ (ซึ่งไม่ชัดเจนจากคำตอบอื่น ๆ ):

$ npm install --save lodash

$ npm install --save @types/lodash

จากนั้นนำเข้าในไฟล์ ts ของคุณ:

import * as _ from "lodash";

สิ่งนี้จะสร้างคำเตือนใน Angular 10: คำเตือนใน /.../test.component.ts ขึ้นอยู่กับ "lodash" การพึ่งพา CommonJS หรือ AMD อาจทำให้เกิดการเพิ่มประสิทธิภาพ bailouts
Adrian Madaras

7

ตามที่คนอื่น ๆ ได้ชี้ให้เห็นแล้วการใช้ lodash หรือขีดล่างน่าจะเป็นทางออกที่ดีที่สุด แต่ถ้าคุณไม่ต้องการไลบรารีเหล่านั้นสำหรับสิ่งอื่นคุณอาจใช้สิ่งนี้ได้:

  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.

  }

นั่นคือสิ่งที่เราตัดสินใจทำ


1
// เพื่อยกเลิกการเชื่อมโยงวันที่เราสามารถเพิ่ม: if (Object.prototype.toString.call (obj) === '[object Date]') {return new Date (obj.getTime ()); }
A_J

1
หรือตรวจสอบวันที่โดยใช้ประเภทอินสแตนซ์ - if (obj instanceof Date) {return new Date (obj.getTime ())}
Anoop Isaac

0

ฉันต้องการคุณสมบัตินี้เพียงแค่สร้างโมเดลแอปของฉัน (ข้อมูลแบ็กเอนด์ดิบที่แปลงเป็นวัตถุ) ดังนั้นฉันจึงใช้การรวมกันของObject.create (สร้างวัตถุใหม่จากต้นแบบที่ระบุ) และObject.assign (คุณสมบัติการคัดลอกระหว่างวัตถุ) ต้องจัดการสำเนาลึกด้วยตนเอง ฉันสร้างส่วนสำคัญสำหรับสิ่งนี้


0

มีปัญหาเดียวกันและไม่ต้องการใช้ปลั๊กอินใด ๆ เพียงเพื่อการโคลนลึก:

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

เครดิต: ฉันทำให้ฟังก์ชันนี้อ่านง่ายขึ้นโปรดตรวจสอบข้อยกเว้นของฟังก์ชันการทำงานด้านล่าง


0

ฉันเช่นเดียวกับคุณประสบปัญหาในการทำงาน angular.copy และ angular.expect เนื่องจากไม่ได้คัดลอกวัตถุหรือสร้างวัตถุโดยไม่เพิ่มการอ้างอิงบางอย่าง ทางออกของฉันคือ:

  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()วิธีการแปลงวัตถุ JavaScript หรือมูลค่าให้กับสตริง JSON


2
นี่ได้กล่าวอย่างละเอียดถี่ถ้วนแล้วข้างต้นว่านี่เป็นวิธีที่แย่ที่สุดในการรักษา!
Alessandro

0

คุณสามารถโคลน Array ได้เช่น

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

และโคลนวัตถุเช่น

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

สิ่งนี้ผิดเนื่องจาก angularjs.copy รองรับ deepCopy ดังนั้นคำตอบของคุณจึงทำให้เข้าใจผิดเนื่องจากไม่รองรับ deepClone ของวัตถุ
Rambou

0

หากคุณยังไม่ได้ใช้ lodash ฉันไม่แนะนำให้ติดตั้งเพียงวิธีเดียวนี้ ฉันขอแนะนำให้ใช้ไลบรารีเฉพาะที่แคบกว่าเช่น 'โคลน' แทน:

npm install clone

0

ฉันได้สร้างบริการเพื่อใช้กับ Angular 5 หรือสูงกว่ามันใช้angular.copy()ฐานของ angularjs มันทำงานได้ดีสำหรับฉัน นอกจากนี้ยังมีฟังก์ชั่นอื่น ๆ เช่นisUndefinedฯลฯ ฉันหวังว่ามันจะช่วยได้ เช่นเดียวกับการเพิ่มประสิทธิภาพใด ๆ ก็น่าจะดีที่ได้ทราบ ความนับถือ

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.