-1

Say I have a behavior subject, piped through an operation or two:

const inputNumber = new BehaviorSubject(2);

// The current number can be retrieved with `inputNumber.getValue()`

const numberAfterOperations = inputNumber.pipe(
  filter(a => (a+1) % 2), // filter out odd numbers
  map(a => a * a), // square it
);

How can I access the value of numberAfterOperations?

// Does not work because `getValue` is not defined!
numberAfterOperations.getValue(); // I expect this to be 4

inputNumber.next(4);

// Does not work!
numberAfterOperations.getValue(); // I expect this to be 16

Note: the observable subscription here works fine. I'm asking about synchronous access to the .value (or .getValue())

I noticed a similar issue here, but did not see a resolution: https://github.com/ReactiveX/rxjs/issues/2378

Eric Vicenti
  • 1,928
  • 1
  • 16
  • 16
  • To be clear, you are not piping the `BehaviorSubject` itself, only it's value. Thus, the value of `inputNumber` will not be updated in the `map(a => a * a)` and it sounds like that's what you're expecting to happen? – sellmeadog Feb 06 '19 at 22:22

4 Answers4

2

I think you're confused about how Observables/Subjects work. The Subject doesn't hold the value of 4 or 16 in the examples you've shown, those are derived by applying your piped operations through the resulting observables. You'll have to use an operator like first() or take(1) to get the value out in between calls to next on the Subject. The Subject only knows about the values that have been sent through it with next it has no knowledge about how observables that may be attached to it decide to modify that value.

Jesse Carter
  • 20,062
  • 7
  • 64
  • 101
1

Observable.pipe() returns Observable, thus if you want to have access to the actual BehaviourSubject, you must pass it too.

const inputNumber = new BehaviorSubject(2);

// The current number can be retrieved with `inputNumber.getValue()`

const numberAfterOperations = inputNumber.pipe(
  filter(a => (a+1) % 2), // filter out odd numbers
  map(a => a * a), // square it
  map(n => [n, inputNumber])
);

// ...later

numberAfterOperations.subscribe(([number, subject]) => {
  sbject.next(n/2);
});

But this should not be done, you can clearly see the anti-pattern. Make .next into a separate method and import it where needed.

Akxe
  • 9,694
  • 3
  • 36
  • 71
1

Pipe sets up a new observable that doesn't do anything until it is subscribed to. It is not a behavior subject that the value can just be accessed with getValue. You have to subscribe to it or it is a cold observable and there is no data going down the stream at all. Put a console.log in your map function and you will see that it doesn't even get called if there is no subscription.

You could write an unwrap function if you are 100% sure that the subscription will run synchronously as it will if derived from a BehaviorSubject but if you pass in an asynchronous observable the results will be unpredictable, most likely returning undefined.

const { BehaviorSubject } = rxjs;
const { filter, map } = rxjs.operators;

const inputNumber$ = new BehaviorSubject(2);

const numberAfterOperations$ = inputNumber$.pipe(
  filter(a => (a+1) % 2), // filter out odd numbers
  map(a => { console.log('mapping'); return a * a; }), // square it
);

inputNumber$.next(4);

console.log('No mapping has happened yet');

console.log(unwrap(numberAfterOperations$));

function unwrap(obs$) {
  let value;
  const sub = obs$.subscribe(val => { value = val; });
  sub.unsubscribe();
  return value;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>

If you are using Angular you can use the async pipe in the view

{{ numberAfterOperations | async }}
Adrian Brand
  • 20,384
  • 4
  • 39
  • 60
0

Thanks for the answers, folks. You have helped me realize that there is no built-in way to do what I wanted with behavior subjects. I wound up with the following:

function mapBehaviorSubject(behavior, mapFn) {
  const mappedSubject = new BehaviorSubject(mapFn(behavior.getValue()));
  behavior.subscribe({
    next: value => mappedSubject.next(mapFn(value)),
  });
  return mappedSubject;
}

This allows me to have the desired behavior:

const inputNumber = new BehaviorSubject(2);

const numberAfterOperations = mapBehaviorSubject(inputNumber, x => x * x);

This allows me to have a persistent subject with the mapped value available synchronously with getValue().

Eric Vicenti
  • 1,928
  • 1
  • 16
  • 16