Home > Software engineering >  Toggling value of behavior subject without using getValue
Toggling value of behavior subject without using getValue

Time:01-29

I want to toggle the boolean value of my behavior subject. I'm currently achieving this by using the Behavior Subject's getValue() method. This isn't ideal as I'm effectively taking my reactive, declarative code and turning it into imperative style code by pulling the data out of the data stream:

private userIsRegistering: BehaviorSubject<boolean> = new BehaviorSubject(false);
userIsRegistering$: Observable<boolean> = this.userIsRegistering.asObservable();

toggleUserIsRegistering() {
   this.userIsRegistering.next(!this.userIsRegistering.getValue());
}

I want to do this in a reactive way by somehow making use of RXJS operators and working within the data stream. I can't seem to figure how to go about this?

I've seen this SO post:

RXJS - BehaviorSubject: proper use of .value

But the issue with this is that the myBool$ Observable only gets triggered when the myToggle$ Observable is triggered.

I need the userIsRegistering Subject/Behavior Subject to have an initial value as I am deriving state from this Observable in other parts of the service:

accountAction$: Observable<{type: string, linkLabel: string, linkText: string, buttonAction: string}> = this.userIsRegistering$.pipe(
    map(userIsRegistering => {
      return {
        type: userIsRegistering ? 'Register' : 'Login',
        linkLabel: userIsRegistering ? 'Already have an account?' : 'No account?',
        linkText: userIsRegistering ? 'Login' : 'Register',
        buttonAction: userIsRegistering ? 'Register' : 'Login',
      }
    })
  );

If the Behavior Subject did not have an initial value, the above accountAction$ Observable would not be triggered when the service initializes.

Anyone have any ideas?

CodePudding user response:

The proper reactive way to achieve this toggle is withLatestFrom pipeable operator IMHO. You need something like this:

anotherObservable$.pipe(
  withLatestFrom(this.userIsRegistering)
).subscribe(userIsRegisteringValue => {
  this.userIsRegistering.next(!userIsRegisteringValue);
})

Here you can find withLatestFrom documentation: https://www.learnrxjs.io/learn-rxjs/operators/combination/withlatestfrom

Because of BehaviourSubject dual nature you don't need .asObservable()

I need the userIsRegistering Subject/Behavior Subject to have an initial value

In your case userIsRegistering already has initial value which is false.

CodePudding user response:

But the issue with this is that the myBool$ Observable only gets triggered when the myToggle$ Observable is triggered.

If you simply need to ensure derived observables emit an initial value, just use startWith:

  private myToggle$ = new Subject<void>();

  private myBool$ = this.myToggle$.pipe(
      scan(previous => !previous, true),
      startWith(true),
  );

  state$ = this.myBool$.pipe(
    map(bool => bool ? STATE_A : STATE_B)
  );

Here, myToggle$ is simply a trigger (emitted value is not used). myBool$ uses scan to emit the new state based on the previous emission. startWith will initially emit the default value.


If you need the multicast / replay functionality like BehaviorSubject provides, you could simply use shareReplay(1):

  private myBool$ = this.myToggle$.pipe(
      scan(previous => !previous, MY_BOOL_INITIAL),
      startWith(MY_BOOL_INITIAL),
      shareReplay(1)
  );

Here's a little StackBlitz demo.

CodePudding user response:

Sometimes it's not worth it to create a separate "toggle" mechanic, and it's simpler just "set" the boolean value instead.

In this case, you would need to emit both the "state" and the value of "myBool", so we can wrap them into a single view model object:

  private myBool$ = new BehaviorSubject<boolean>(true);

  vm$ = this.myBool$.pipe(
    map(myBool => ({ 
      state: myBool ? STATE_A : STATE_B, 
      myBool
    }))
  );

  setMyBool(value: boolean) {
    this.myBool$.next(value);
  }

Then from your template you can call setMyBool(!vm.myBool).

Here's a working StackBlitz demo.

  • Related