1

I'm working on an angular2 app that takes advantage of several services to share date throughout the app.

In one part of my app, I have an edit function that allows the user to modify the data in an array.

In this case, when the user modifies a property in the component, the corresponding data in the service is also modified without setting it directly.

I'm about 6 months in to learning angular, but I know that setting one array equal to another is actually pointing at the same object. So in this case, I have a method in the service that returns the array as a slice(), which is I believe is supposed to create new array to avoid the user being able to modify the service data directly.

The full app that I'm working on is fairly complex, but I was able to create a bare bones angular app to demonstrate what is happening.

test.component.html:

<div *ngFor="let item of testComponentData">
      <p>{{ item.fname }} {{ item.lname }} <button (click)="onUpdate(item.id)">Update</button></p>
</div>

test.model.ts:

export class TestObject {
  constructor(public id: string, public fname: string, public lname: string) {}

test.service.ts:

@Injectable()
export class TestService {

  constructor() { }


  private serviceData: TestObject[] = [
    new TestObject('1', 'Joe', 'Smith'),
    new TestObject('2', 'Mary', 'Jones'),
    new TestObject('3', 'Larry', 'Murray'),
  ];

  getData() {
    return this.serviceData.slice();
  }
}

test.component.ts:

export class TestComponent implements OnInit {
  testComponentData: TestObject[];

  constructor(private testService: TestService) { }

  ngOnInit() {
    this.testComponentData = this.testService.getData();
  }

  onUpdate(id) {

    // Clicked 3rd button, id = 3

    const temp = this.testComponentData.find(o => o.id === id);

    console.log(this.testService.getData());

    // 0: TestObject {id: "1", fname: "Joe", lname: "Smith"}
    // 1: TestObject {id: "2", fname: "Mary", lname: "Jones"}
    // 2: TestObject {id: "3", fname: "Larry", lname: "Murray"

    setTimeout(() => {
      temp.fname = 'Bartholomew';
      console.log(this.testService.getData());
    }, 5000);

    // 0: TestObject {id: "1", fname: "Joe", lname: "Smith"}
    // 1: TestObject {id: "2", fname: "Mary", lname: "Jones"}
    // 2: TestObject {id: "3", fname: "Bartholomew", lname: "Murray"}

  }

}

In the component, the testComponentData property is initialized in ngOnInit by calling the testService.getData() method, which returns this.serviceData.slice()

In this example, I am clicking the 3rd button to set the fname to 'Bartholomew.' As you see in the embedded comments, the testService data changes, even though I am only changing the component object (testComponentData), the service data is also being changed (this.testService.getData())

Timeout is just in there because sometimes the first console.log is laggy and log will show the value has changed already.

I can't for the life of me see how this is happening. I know there is something fundamental here, and I assume I am accessing the same object somehow, but I don't understand why.

Any help is appreciated. Thank you!

Jim Siwek
  • 13
  • 2

3 Answers3

0

This is because Array.slice on an array of objects is creating a new array who's values are references to the original objects. You will not see this functionality with something like a string or number array. You can get more information from this answer about shallow copying arrays.

0

Empty .slice() call is a known shortcut of replicating an array. Similar to using the spread [...someArray] operator. However, since the contents of the array are objects, they are returned by reference into the new array.

Basically, both the arrays i.e. serviceData in service and temp in component are sharing the same object references.

Hence updating a value of an item in temp is also reflecting in the serviceData array.

ashish.gd
  • 1,713
  • 1
  • 14
  • 25
0

The object will change because of the same reference of the object. now need to clone if you do not want to effect of the original object. Try to following code

Declare the clone array:

clonedTestComponentData: TestObject[];

Clone an Array:

const this.clonedTestComponentData  = Object.assign([], this.testComponentData);

Clone an object :

const temp = Object.assign({}, this.testComponentData.find(o => o.id === id));
Shohel
  • 3,886
  • 4
  • 38
  • 75
  • Thanks Shohel! I appreciate you taking the time to give code examples how to clone the objects & arrays. – Jim Siwek Apr 03 '19 at 16:46