Eric Huang
Eric Huang

Reputation: 1264

Angular rxjs BehaviorSubject.value sets value without next(), not immutable

While I was testing my code, I accidentally ran into unexpected mutation.... Or maybe... I am just doing it all wrong...

User

constructor(
public id: number,
public education: Education[]

){}

UserStateService

private user = new BehaviorSubject<User>(null);

setUser(user:User){
  // set by HttpClient or perform an update
  this.user.next(user);
}

getUserDetail(){
  return this.user.value; 
       // this.user.getValue();  => tried this as well same thing...
}

updateUserDetail(user:User){
  // Maybe perform some check method before the update
  this.user.next(user);
  // HttpClient to save on db
}

I have a form in my component that user will modify their own data. So my idea here is to call getUserDetail() thinking that the return object should be readonly. Once I have set the new value I would then updateUserDetail() to update the observable with next(), but I experienced otherwise...

Component

onSubmit(){

 let currentUser = this.userService.getUserDetail();

 console.log("Original User => ", currentUser);  // array(2) see screen shot highlighted

 currentUser.educations = this.userForm.value['educations'];

 console.log("Modify User => ", currentUser); // array(1)

 // this.userService.updateUserDetail(currentUser); 

}

enter image description here

currentUser.educations = this.userForm.value['educations']; I don't want this to automatically update the observable, because there will be times where I might need to validate information before making the change.... how can I achieve this?

Thank you

Upvotes: 7

Views: 8633

Answers (2)

frido
frido

Reputation: 14139

You have to make a copy of the BehaviorSubjects current value before modifying it.

// e.g. for a Set
const behaviorSubject = new BehaviorSubject<Set<string>>(new Set());
// create copy
const set = new Set(this.behaviorSubject.getValue());
// modify
set.add('item');
// update BehaviorSubject
behaviorSubject.next(set);
// e.g. for an object
const behaviorSubject = new BehaviorSubject<any>({});
// create copy
const value = { ...this.behaviorSubject.getValue() };
// modify
value.a = 'item';
// update BehaviorSubject
behaviorSubject.next(value);

Upvotes: 1

Eric Huang
Eric Huang

Reputation: 1264

I am not sure if this is the correct way... If you find this solution to be a bad practice please state the problem and also provide the solution. I am happy to change the marked answer.

But this is how I did it.

I basically have another variable to act sort of like a liaison to my subjects. This created numerous benefits such as validating data before .next() observable.

This is what the code looks like

private userSubject = new BehaviorSubject<User>(null);
user$ = this.userSubject.asObservable(); // -> prevent .next() be called on the component 
private user: User = null;

setUser(user:User){
  this.user = user
  // if(user) -> so some check if you desire
  this.userSubject.next({...this.user}); // use spread syntax to make a copy so it does not directly refere to this.user
}

// -> Don't need this any more. Just subscribe to user$
// getUserDetail(){
//  return this.user.value; 
       // this.user.getValue();  => tried this as well same thing...
//}

The following solution was inspired by this article https://codeburst.io/javascript-es6-the-spread-syntax-f5c35525f754

Document link for spread syntax https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax

Upvotes: 3

Related Questions