0

I'm building a little d&d app to practice passing data components in angular and I'm hitting a strange issue.

I'm getting a "monster" object back from an API call and then populating that to the view a certain number of times. I'm trying to change one of the values, "name" by adding an index - simple - get "name" add and index. I'm getting the value for "name" changing it, and then pushing it to an array held in the component - all works fine. I'm thinking that the original value should be unchanged for the next iteration of the loop but this is not the case. Something i'm not understanding in scope here.

trying to only post a bit of the code as I know where the issue is and I know its a scope issue I just want to know why this is happening as a concept as Its something I'm not familiar with. Using

export class CombatComponent implements OnInit {
  selected:number;
  monsters:any[] = []
  addedMonsters:any[] =[]

  monsterNumber:number = 0;    <----this is being passed from a form - its working

  constructor( private ms:MonsterService) { }

 <-dont want to post the entire function because this is where the issue lies ->

this.$monsterData.subscribe(data =>{
 this.currentMonster=data;

 for (let i = 0 ; i<this.monsterNumber; i++){

   let originalName = this.currentMonster.name;
   console.log(this.currentMonster.name)

   this.monsters.push(this.currentMonster)
   let newName = this.monsters[i]['name'] = this.currentMonster.name +" " + `${i+1}`
   this.monsters[i].name = newName

   console.log(this.monsters)

 }

My issue is that after the first Iteration, what I think should happen is an array with a name + an index. Whats really happening is that the second time through the "name" value for currentMonster gets changed to the original value + index. What is it about scope im not understanding here?

Ayyash
  • 4,257
  • 9
  • 39
  • 58
Timotronadon
  • 315
  • 1
  • 2
  • 15
  • this.monsterNumber is always set to 0, you have to increment the value or set this.monsterNumber = this.monsters.length. – Saurabh Yadav Nov 21 '19 at 20:57
  • its actually not - it comes from a form that is actually working. If lets say that number is 4 - rather than 4 items being "item 1" "item 2" etc - its 4 "item 1 2 3 4"s – Timotronadon Nov 21 '19 at 20:58
  • @SaurabhYadav I thought that too but the question tells the for loop is getting hit so I assumed the code is left off. – A1rPun Nov 21 '19 at 20:58
  • @A1rPun, yeah the loop is hitting fine and being passed from a form on the page – Timotronadon Nov 21 '19 at 21:00

3 Answers3

2

You are pushing the same monster object onto the array each iteration. This causes array elements like this.monster[1] and this.monster[2] to hold the same object as each other. Because of this if you change monster[1] you are changing monster[2] and so on (including currentMonster as they all reference the same object)

I assume you are actually trying to create new monster objects based off the currentMonster properties and trying to assign them a new name.

//these do shallow copy of the properties
//this is regular javascript might have to make
//changes to fit typescript syntax

//using spread operator to create new object
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
let newMonster = {...this.currentMonster}
//using Object.assign() to create new object
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
let newMonster = Object.assign({},this.currentMonster);

Then just change the appropriate properties and push the new object onto the array.

//give new monster a name 
newMonster.name = this.currentMonster.name +" " + `${i+1}`;

//push the new monster on the array
this.monsters.push(newMonster);
Patrick Evans
  • 41,991
  • 6
  • 74
  • 87
  • 100%! I understand now what I was trying to do was push a reference and not a copy, correct? Is there a term for this dynamic? – Timotronadon Nov 21 '19 at 21:31
  • Yes you were pushing a reference and not a copy. See [Is JavaScript a pass-by-reference or pass-by-value language?](https://stackoverflow.com/questions/518000/is-javascript-a-pass-by-reference-or-pass-by-value-language) for a q&a on the topic of what is being passed around – Patrick Evans Nov 21 '19 at 21:38
  • @Timotronadony yes the term is "copying" versus "cloning"... welcome to the dark side of javascript :) – Ayyash Nov 22 '19 at 03:47
  • my god. Thanks people this would've broken my face. – Timotronadon Nov 22 '19 at 15:57
1

When you do this:

 this.monsters.push(this.currentMonster)

You aren't pushing a copy of the currentMonster object to the array you are pushing a reference to the object. At this point that means that:

 this.monsters[0] === this.currentMonster

So then when you mutate the name property:

this.monsters[i].name = newName

You are also mutating the currentMonster.name property

What you likely want to do is create a copy of the currentMonster object and change the name only:

this.monsters.push({
     ...this.currentMonster,
     name: `${this.currentMonster.name} ${i+1}`
})
noj
  • 6,740
  • 1
  • 25
  • 30
0

var arr = []; // create arry 
var obj = {ima:"object"}; // create object
arr.push(obj); // pass same object 3 times
arr.push(obj);
arr.push(obj);

console.log(arr); // log arr 
// will print : [
// {
//  /**id:2**/
//    "ima": "changed object"
//  },
//  /**ref:2**/,
//  /**ref:2**/
// ]
obj.ima = "changed object"; // change the object 

console.log(arr); // log the array, notice that object inside the array will be modified

arr[0].ima = "changed in the arr"; // then modify the object inside the array

console.log(obj); // notice original object will be modified.

arr.push(Object.assign({},obj)); // this is the copy or use ... operator in typescript

console.log(obj);

arr[1].ima = "something newer";

console.log(obj); // "remains same"

Code above is same like you do in your code. You are passing references not the copied values.

Eldar
  • 9,781
  • 2
  • 10
  • 35