11

Inside a component I tried to copy an object from a service that could be modified inside the component, but should stay in the service.

private test;

public ngOnInit(): {
  console.log(this.someService.test.someProperty) //'old'
  this.test = this.someService.test;

  //not working as well?!
  //this.test = Object.assign({}, this.someService.test);
  //this.test = Object.create(this.somerService.test);

  this.changeTest();
}

public changeTest(): void {
  this.test.someProperty = 'new';
}

after the init both, this.test.someProperty as well as this.someService.test.someProperty are changed to new even though the last should stay old?

Why is that happening, and how to only change properties of this.test

Max Solid
  • 1,213
  • 3
  • 21
  • 32
  • how are you getting this object from the service... is it an observable?\ – JayDeeEss Sep 20 '17 at 18:27
  • no its a normal object defined like `public test: any = {...}` – Max Solid Sep 20 '17 at 18:30
  • maybe thats the issue, its passing the reference as well when you are assigning it to your local test. just a guess ...not sure, what you can do to avoid it to create a private subject in your service and expose public observable instead! – JayDeeEss Sep 20 '17 at 18:32
  • 1
    is there no way copying objects without a reference ?! – Max Solid Sep 20 '17 at 18:35
  • 1
    I think Object.create(obj) should work.. what about this.test = JSON.parse(JSON.stringify(this.someService.test)); – bgraham Sep 20 '17 at 18:38

3 Answers3

25

It's because Object.assign is a shallow merge, it's only merging the top level, so the top level is referencing the same object in both the service and the component.

I personally use lodash's merge when I need to deep merge objects:

this.test = _.merge({}, this.someService.test)

Also see: How to deep merge instead of shallow merge?

You can also deep clone the object with:

this.test = JSON.parse(JSON.stringify(this.someService.test));

Also see: How to Deep clone in javascript

Eeks33
  • 2,245
  • 1
  • 14
  • 17
  • 1
    wow. thanks. this saves my day! (this.test = JSON.parse(JSON.stringify(this.someService.test)); ) – kamiyar Apr 23 '20 at 16:16
16

Actually Object assign works as in your example. Created plunker to prove it

  this.test = Object.assign({}, this.someService.test);
alexKhymenko
  • 5,450
  • 23
  • 40
1

I personally prefer to add a clone function in the class. It iterates through the object fields and assign the matching values.

export class Car {

    constructor(
        public id: number, 
        public name: String) { }

    clone(other : Car) : void {
        let k: keyof Car;
        for (k in other) {
            const v = other[k];
            (this[k] as (typeof v)) = v;
          }
    }
}

Usage :

var car1 = new Car(1, "Amg");
var car2 = new Car(2, "205");
car1.clone(car2);
Hocine B
  • 391
  • 2
  • 8