Reputation: 682
I'm trying to realize a kind of a counter with RXJS. There's an subject (number) which can be incremented or reduced step by step. At the end, it should emit the counter value, which can be positive oder negative.
As shown in the example below, the inputs are debounced by one second. Which means, the observable emits after one second of inactivity and returns the sum of the values.
const mySubject$: Subject<number> = new Subject<number>;
get counter$(): Observable<number> {
return this.mySubject$.pipe(
buffer(this.mySubject$.pipe(debounceTime(1000))),
map(steps => steps.reduce((accumulator, currentValue) => accumulator + currentValue, 0))
);
}
increment(): void {
this.mySubject$.next(1);
}
reduce(): void {
this.mySubject$.next(-1);
}
Good so far... But now, i want to show the current counter value in realtime somewhere on increment/reduce, before it got emitted a second later. Is there a way to access the buffered array, before the debounce time is over?
I could create another counter variable outside of the observable and put the value there within a tap() before the buffer(). But this seems not to be the RXJS way?
const mySubject$: Subject<number> = new Subject<number>;
const counter: number = 0;
get counter$(): Observable<number> {
return this.mySubject$.pipe(
tap(value => this.counter += value),
buffer(this.mySubject$.pipe(debounceTime(1000))),
map(steps => steps.reduce((accumulator, currentValue) => accumulator + currentValue, 0)),
tap(() => this.counter = 0)
);
}
Any ideas for a clean way, how to do this?
Purpose of the code: I have an app running on a tv. During streaming, the user is able to "jump seek" in steps of 30 seconds back or forward with two dedicated keys on the remote. If the user presses the button for example 3 times in a row, the stream jumps 90 seconds back or forward. There is a delay of one second between the key press action, before the accumulated value should emit. But every time the user presses the button, theres an visible output (like a toast) of how many seconds the jump seek would be on emit.
Upvotes: 0
Views: 1176
Reputation: 7360
I would make use of one subject (_jumpBy$
), used to trigger the commands, and two mapped observable: one (jumpByToShow$) shows immediately the values, accumulated via scan
, the other debounces the emissions of the first and, upon emitting a value, zeroes out the accumulated value making the subject emit a -amount
value.
Untested, hope it helps.
private _jumpBy$ = new Subject<number>();
// the quantity changing immediately, to be shown on screen
public jumpByToShow$: Observable<number>;
// the quantity debounced
public jumpByToExecute$: Observable<number>;
constructor(/* ... */) {
this.jumpByToShow$ = this._jumpBy$.pipe(
startWith(0),
scan((amount, newAmount) => (amount + newAmount), 0),
share()
);
this.jumpByToExecute$ = this.jumpByToShow$.pipe(
debounceTime(1000),
filter(amount => amount !== 0),
tap(amount => this._jumpBy$.next(-amount))
);
}
Upvotes: 0
Reputation: 1485
You can maybe try with scan, which will do the summing work for you, so you can prompt it somewhere and still debounce the output.
get counter$(): Observable<number> {
return this.mySubject$.pipe(
scan((acc, delta) => delta ? acc + delta : 0, 0),
tap((value) => *you can use the total accumulator so far here*),
debounceTime(1000),
);
}
To reset the counter you just send a null value through.
Upvotes: 1