Intransparant state mutations of BehaviourSubjects is possible via map(...)
and tap(...)
#7349
Replies: 2 comments 1 reply
-
I also noticed this. I also agree that the documentation could be clearer about this. I believe this can be solved to some extent in the application space using typescript: // lifted directly from this article: https://dev.to/bwca/deep-readonly-generic-in-typescript-4b04
type DeepReadonly<T> = Readonly<{
[K in keyof T]:
// Is it a primitive? Then make it readonly
T[K] extends (number | string | symbol) ? Readonly<T[K]>
// Is it an array of items? Then make the array readonly and the item as well
: T[K] extends Array<infer A> ? Readonly<Array<DeepReadonly<A>>>
// It is some other object, make it readonly as well
: DeepReadonly<T[K]>;
}>
// operators on this subject should HAVE to create a copy in case they mutate the data
const subj = new BehaviorSubject<DeepReadonly<{ a: string}>>({ a: 'test' });
// line below gives: Cannot assign to 'a' because it is a read-only property.ts(2540)
subj.pipe(map(data => data.a = 'overwritten')).subscribe(...); what do you think? |
Beta Was this translation helpful? Give feedback.
-
Honestly, I don't think we'd want to do this, as immutability can be composed in, rather than baked in. The main reason I would want to avoid this is performance. Duplicating objects and the GC there in? You might want to look into |
Beta Was this translation helpful? Give feedback.
-
Describe the bug
When mutating a value in map or tap by regular object and field assignment, the whole state of the Behaviour subject will change.
While it is expected that mutating a Object will directly change the object, it is intransparent, when using a Behavioursubject as a sources and passing it multiple locations as Observable.
When mapping and subscribing to an observable that is derived from a Behavioursubject, it will subsequently effect all other Observables in other places aswell that listen to the same value.
See the code example.
This can lead to dangerous and unexpected bugs in large Projects (for example with Angular) in which multiple teams do this.
Proposals are:
Which when "muted" will throw errors when mutated. Or be cloned from the original state when created such an observable, or on default allow any mutation
Expected behavior
Objects mutations within tap(...) or map(...) of derived obserables from Behavioursubjects should not mutate the original state of the Behavioursubject
Reproduction code
Reproduction URL
https://stackblitz.com/edit/rxjs-xgtmg6?file=index.ts
Version
7.8.1
Environment
NodeJS, Angular, Javascript
Additional context
No response
Beta Was this translation helpful? Give feedback.
All reactions