1

Suppose I have a behavior subject in my service:

theRuleSbj = new BehaviorSubject<Rule>(null);

Then in my component I get that value by using getValue() method:

this.rule = this.ruleService.theRuleSbj.getValue();

Then if I change some data in my local rule object. for example:

this.rule.name = 'name';

My behavior subject value also change without calling next! It seems that my variable was bound to the behavior subject so every change in my local variable will affect my behavior subject. Is this a subject behavior behavior!!?? or I do something wrong? (Suppose that I don't want to change the behavior subject when changing my local variable).

Update:

I tested the solution in the marked answer and it works with a simple object. But actually as in my case if you have a nested objects (objects inside object) this is not working and I found the "deep copy" term in this link that works perfectly for me.

Hanif
  • 93
  • 1
  • 1
  • 9

3 Answers3

4

To get your value pushed out of the Subject, so create an Observable from it and subscribe.

If you want a local version of the value, you need to take a copy due to objects being passed by reference in JavaScript, so create a new object when you subscribe.

You can do this with Spread Syntax.

Then you can assign whatever values you like to the local object without effecting the Subject.

e.g. (StackBlitz)

    const theRuleSbj = new BehaviorSubject<Rule>(null);
    const theRule$ = theRuleSbj.asObservable(); 
    
    // The observable will emit null on the initial subscription
    // Subject might be a better fit
    theRule$.subscribe(rule => {
        console.log(`Subscription value:`, rule);
        // Use object spread to create a new object for your component
        this.rule = { ...rule };
      });
    
    // Update the Subject, it will console log new value
    // and update your local value
    theRuleSbj.next({ name: 'Name 1'});
    
    // Update your local value, does not effect your Subject value
    this.rule.name = 'Name 2'; 
    
    // Confirm that they are differant
    console.log(`Subject Value:`, theRuleSbj.getValue());
    console.log(`Local Value`, this.rule);
    
    // Be careful as when you update the Subject, your local value will be updated
    theRuleSbj.next({ name: 'Name 3'});
    console.log(`Subject Value (after subject update):`, theRuleSbj.getValue());
    console.log(`Local Value (after subject update)`, this.rule);

Be warned, having subscribed you will get all updates to the subject value pushed to your local value, you may or may not want this to happen.

If you only want the one value in the component, you can pipe() the observable and use take(1) to get one value out, but as you initialise the Subject as BehaviourSubject, you will only get the null value. You may want to change this to a Subject so when the first value is pushed to the Subject your component receives it.


    const theRuleSbj = new Subject<Rule>();

    /* other code omitted  */

    theRule$
        .pipe(take(1))
        .subscribe(rule => {
            console.log(`Subscription value:`, rule);
            this.rule = { ...rule };
        });
    
markfknight
  • 386
  • 3
  • 7
  • Thanks for your reply and effort. Your solution is worked but as I described in my update I have a nested object in my behavior subject so spread syntax is not working for me. I found 'deep copy' solution [here](https://www.codementor.io/@junedlanja/copy-javascript-object-right-way-ohppc777d#deep-copy-using-jsonstringify-and-jsonparse) that works for me. – Hanif Apr 12 '20 at 09:20
1

Object properties are passed by reference in Javascript. See here for more details. That said, accessing an observable using getValue() isn't so elegant. You could subscribe to get the value without modifying the source.

Service

theRuleSbj = new BehaviorSubject<Rule>(null);
ruleObs = this.theRuleSbj.asObservable();

Component

this.ruleService.ruleObs.subscribe(rule => { this.rule = rule });
ruth
  • 29,535
  • 4
  • 30
  • 57
  • Thanks for your reply. Your answer is really help me to understand what is going on. Specially with the link you provided in your answer. Now, I know what is the problem but your solution is not working for me. – Hanif Apr 09 '20 at 05:20
  • I mean even if I declare a new observable with my behavior subject the problem still remains. As the link in your answer points out, If I change the object by property like : this.rule.name = 'name' the subject will change, but if I change the whole object: this.rule = { name = 'name' }, the behavior subject will not change. – Hanif Apr 09 '20 at 05:26
0

This is because BehaviorSubject returns you a link to it's current value. When you do something like

this.rule.name = 'name';

object property value changes, but link shared for everyone still the same. If you need locally change rule value try

this.rule = {...this.rule, {name: name}}

it overrides your local link without changing source value

Max Ivanov
  • 133
  • 1
  • 9
  • Thanks for your reply. It works for me and with @Micheal D answer I understood what exactly is going on. – Hanif Apr 09 '20 at 05:29