I'm very new to rxjs and declarative programming in general, so apologies if this is a foolish question!
I am, in Typescript, trying to implement behaviour similar to that of a digital register, in that I have two input boolean observables, let's say A and B, and an output register O (also boolean)
Functionality:
What I am trying to do is, when B becomes true, save the value of A, so that when B becomes false again I can then use the value of A
I've seen this question, but I'm not able to bridge the gap from its answer to my requirement
I think that I can use pairwise() on B to detect the case when B goes from FALSE to TRUE (previousValue === false && currentValue === true) in conjunction with withLatestFrom to query the current value of A and update the value of O.
What I do not understand is how, in all other cases, I can re-emit/retain the current value of O. As far as I can tell, there is no way to tell, within a pipe operator when defining the Observable O, to reference its current value
The quickest way I could find to fulfil your requirements is to come up with the following:
a$
and b$
as BehaviorSubject
and ReplaySubject
respectively.
a$: BehaviorSubject<string>
: Emit either default value or the last value emitted to it (using eg. a$.next()
) to it's subscribers including future subscribers. If you wish for a different behavior you could change it either with ReplaySubject
with buffer 1 (emit the last emitted value but without a default value) or Subject
(does not emit previous emission to future subscribers).b$: ReplaySubject<boolean>(1)
: Emit the last emitted value to it's future subscribers.bTransformer$
that takes care of your requirements using the following operators:
distinctUntilChanged
- will only emit when the values of b$
changes i.e. true
-> false
or false
-> true
.pairwise
- emits previous and current emissions as array either [true, false]
or [false, true]
.combineLatestWith(a$)
- combine with the value of a$ eg. [[true, false], 'value of a']
.filter
- emit only when b$
changes from false
-> true
.map
- emit the value of a$
.o$
- starts with value false
using the operator startWith
and emit whenever bTransformer$
emits.Note that with the current implementation, o$
will also emit whenever a value is emitted to a$
since it's wired it using the combineLatestWith$
operator.
Try the following:
const { BehaviorSubject, ReplaySubject, combineLatestWith, distinctUntilChanged, pairwise, filter, startWith, map } = rxjs;
const a$ = new BehaviorSubject('value of a');
const b$ = new ReplaySubject(1);
const bTransformer$ = b$.pipe(
distinctUntilChanged(), // emit only when b$ changes
pairwise(), // emit previous and new values: either [true, false] or [false, true]
combineLatestWith(a$), // combine with the value of a$: eg. [[true, false], 'value of a']
filter(([[bValueOld]]) => !bValueOld), // destructure the pairwise value of b and emit only if it's [false, true]
map(([_, a]) => a) // emit the value of a
);
const o$ = bTransformer$.pipe(
startWith(false), // always start the subscription to o$ with 'false'
);
function onClickB(value) {
b$.next(value);
}
function emitValueA() {
const value = document.getElementById('a-input').value;
a$.next(value);
}
o$.subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.8.1/rxjs.umd.min.js"></script>
<button onclick="onClickB(true)">Emit true to b</button>
<button onclick="onClickB(false)">Emit false to b</button>
<br>
<br>
<input id="a-input"/>
<button onclick="emitValueA()">Emit value to a</button>