0

I am working on an Angular 9, RxJS 6 app and have a question regarding the different outcomes of piping subject values and doing unit conversion in that pipe.

Please have a look at this stackblitz. There, inside the backend.service.ts file, an observable is created that does some "unit conversion" and returns everything that is emmitted to the _commodities Subject. If you look at the convertCommodityUnits function, please notice that I commented out the working example and instead have the way I solved it initially.

My question: When you use the unsubscribe buttons on the screen and subscribe again, when using the "conversion solution" that just overrides the object without making a copy, the values in the HTML are converted multiple times, so the pipe does not use the original data that the subject provides. If you use the other code, so creating a clone of the commodity object inside convertCommodityUnits, it works like expected.

Now, I don't understand why the two ways of converting the data behave so differently. I get that one manipulates the data directly, because js does Call by sharing and one returns a new object. But the object that is passed to the convertCommodityUnits function is created by the array.prototype.map function, so it should not overwrite anything, right? I expect that RxJS uses the original, last data that was emitted to the subject to pass into the pipe/map operators, but that does not seem to be the case in the example, right?

How/Why are the values converted multiple times here?

This is more or less a follow-up question on this: Angular/RxJS update piped subject manually (even if no data changed), "unit conversion in rxjs pipe", so it's the same setup.

Janis Jansen
  • 996
  • 1
  • 16
  • 36
  • If I did not explain my problem well enough, feel free to let me know so I can clarify what I mean. – Janis Jansen May 12 '20 at 07:14
  • If you would, please post all the code relevant to the question *in the question itself* - don't hide it behind a link. You shouldn't tell potential helpers who would otherwise love to help that they have to navigate offsite just to have an idea of what you're working with. If the link breaks, the question could be rendered useless to future readers. Please edit your code into the question in a [MCVE], or the question might get closed, thanks. – CertainPerformance May 12 '20 at 07:15

1 Answers1

1

When you're using map you got a new reference for the array. But you don't get new objects in the newly generated array (shallow copy of the array), so you're mutating the data inside the element.

In the destructuring solution, because you have only primitive types in each object in the array, you kind of generate completely brand new elements to your array each time the conversion method is called (this is important: not only a new array but also new elements in the array => you have performed a deep copy of the array). So you don't accumulate successively the values in each subscription.

It doesn't mean that the 1-level destructuring solution like you used in the provided stackblitz demo will work in all cases. I've seen this mistake being made a lot out there, particularly in redux pattern frameworks that need you to not mutate the stored data, like ngrx, ngxs etc. If you had complex objects in your array, the 1-level destructuring would've kept untouched all the embedded objects in each element of the array. I think it's easier to describe this behavior with examples:

const obj1 = {a: 1};
const array = [{b: 2, obj: obj1}];

// after every newArray assignment in the below code, 
// console.log(newArray === array) prints false to the console

let newArray = [...array];
console.log(array[0] === newArray[0]); // true

newArray = array.map(item => item);
console.log(array[0] === newArray[0]); // true

newArray = array.map(item => ({...item}));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // true

newArray = array.map(item => ({
      ...item,
      obj: {...item.obj}
    }));
console.log(array[0] === newArray[0]); // false
console.log(array[0].obj === newArray[0].obj); // false
julianobrasil
  • 8,954
  • 2
  • 33
  • 55
  • I think I got it, thanks! This absolutely makes sense, though I am a bit worried about that being also the case inside the RxJS pipe. I checked multiple times, that the subject's data was not modified (by subscribing directly to it) and I thought that I could be sure the RxJS pipe will always use the latest emitted value of the subject when the pipe is called, never a previously modified value. What you are saying would mean, that the object is modified through the reference that is stored in the array, but this change is not noticed by the subject as a change, right? – Janis Jansen May 12 '20 at 08:15
  • 1
    Exactly. The subject *is emitting* a new array containing old objects. That's all about references to objects in memory. `Array.prototype.map` only assures that you'll have a new reference to the same array (it allocates a new memory address that contains another memory address as its value: the memory address of the first element of the array). The new content of each element is up to you. – julianobrasil May 12 '20 at 08:26