Observable and how to control results pace - rxjs

I am looking for an operator that would help me pace the results emitted from an observable, it would look like this :
[--A-BC--D-E----------------]
[--A----B----C----D----E----]
I tried AuditTime() but it does not replay the results that was emitted between intervals, it does something like this :
[--A-BC--D-E----------------]
[--A----C----E--------------]
Thanks for your help.

I think this should do what you need:
const e1 = cold('--A-BC--D-E----------------|');
const expected = '--A----B----C----D----E----|';
const source = e1.pipe(
concatMap(val => of(false).pipe(
delay(5, scheduler),
takeWhile(Boolean),
startWith(val),
)),
);
expectObservable(source).toBe(expected);
The trick here is that I'm using concatMap to always wait until the previous Observable completes. The inner Observable emits the value and then postpones its own completion and thus concatMap enforces the delay between two emissions.
See live demo: https://stackblitz.com/edit/rxjs6-test-scheduler?file=index.ts

Related

How does CombineLatest work compared to of

I'm learning about forkJoin. I'm trying to wrap my head around how it works compared to creating an observable with of. Please tell me if I have this right:
When I use of, it creates an observable that will emit the value you pass to it right away. So if I have this:
const obs = of('hello');
obs.subscribe(console.log);
...it will log 'hello' as soon as the line where I subscribe to obs executes. Is this correct?
Now if I have this:
const obs1 = httpClient.get(url1);
const obs2 = httpClient.get(url2);
const fjObs = forkJoin({obs1, obs2});
fjObs.subscribe(({obs1, obs2}) => console.log(`obs1=${obs1}, obs2=${obs2}`));
...it WON'T log obs1 and obs2 as soon as the line where I subscribe to fjObs executes. It will log them only when both obs1 and obs2 have completed, which could be a while after I subscribe to fjObs. Is this correct?
And until that happens, fjObs is just an observable that has not yet emitted any values. Is that correct?
Please let me know if my understanding is correct. Thank you.
Yes you are basically correct. Please note that forkJoin accepts an array of observables, so your code should be: const fjObs = forkJoin([obs1, obs2]); as pointed out in the comment, this assumption was wrong
also you could test this:
forkJoin([of('one'), of('two')]).subscribe(console.log);
in this case the console.log will be executed immediatly since both of emits immediatly.
you could also do: forkJoin([of('one'), httpClient.get(url1)]).subscribe(console.log); in this case it will log after the HTTP request is completed.
You should not compare of with forkJoin since those are very different concepts.
of creates an observable and forkJoin combines an array of observables and emits (the last value of each observable) when all have emitted a value completed

DebounceTime after first value in RxJS

I need a specific behavior that I can't get with the RxJS operators. The closest would be to use DebounceTime only for values entered after the first one, but I can't find a way to do it. I have also tried with ThrottleTime but it is not exactly what I am looking for, since it launches intermediate calls, and I only want one at the beginning that is instantaneous, and another at the end, nothing else.
ThrottleTime
throttleTime(12 ticks, { leading: true, trailing: true })
source: --0--1-----2--3----4--5-6---7------------8-------9---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~I~~~~~~~~~~~]--------
output: --0-----------3-----------6-----------7-----------9--------
source_2: --0--------1------------------2--------------3---4---------
throttle interval: --[~~~~~~~~~~~I~~~~~~~~~~~]---[~~~~~~~~~~~]--[~~~~~~~~~~~I~
output_2: --0-----------1---------------2--------------3-----------4-
DebounceTime
debounceTime(500)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: -----------1--------3--------------------------------13---------
What I want
debounceTimeAfterFirst(500) (?)
source: --0--1--------3------------4-5-6-7-8-9-10-11--13----------------
debounce_interval: -----[~~~~~]--[~~~~~]--------------------------[~~~~~]----------
output: --0--------1--3------------4-------------------------13---------
As you see, the debounce time is activated when a new value is entered. If the debounce time passes and any new value has been entered, it stops the listening the debounceTime action and waits to start a new one.
Edit: I forgot to comment that this must be integrated with NgRx’s Effects, so it must be a continuous stream that mustn't be completed. Terminating it would probably cause it to stop listening for dispatched actions.
I would use a throttle combined with a debounceTime:
throttle: from Documentation Emit value on the leading edge of an interval, but suppress new values until durationSelector has completed.
debounceTime: from Documentation Discard emitted values that take less than the specified time between output.
I would use a throttle stream to get the raising edge (the first emission) and then the debounce stream would give us the falling edge.
const source = fromEvent(document.getElementsByTagName('input'), 'keyup').pipe(
pluck('target', 'value')
);
const debounced = source.pipe(
debounceTime(4000),
map((v) => `[d] ${v}`)
);
const effect = merge(
source.pipe(
throttle((val) => debounced),
map((v) => `[t] ${v}`)
),
debounced
);
effect.subscribe(console.log);
See RxJS StackBlitz with the console open to see the values changing.
I prepared the setup to adapt it to NgRx which you mention. The effect I got working is:
#Injectable({ providedIn: 'root' })
export class FooEffects {
switchLight$ = createEffect(() => {
const source = this.actions$.pipe(
ofType('[App] Switch Light'),
pluck('onOrOff'),
share()
);
const debounced = source.pipe(debounceTime(1000), share());
return merge(source.pipe(throttle((val) => debounced)), debounced).pipe(
map((onOrOff) => SetLightStatus({ onOrOff }))
);
});
constructor(private actions$: Actions) {}
}
See NgRx StackBlitz with the proposed solution working in the context of an Angular NgRx application.
share: This operator prevents the downstream paths to simultaneously fetch the data from all the way up the chain, instead they grab it from the point where you place share.
I also tried to adapt #martin's connect() approach. But I don't know how #martin would "reset" the system so that after a long time if a new source value is emitted would not debounce it just in the same manner as you first run it, #martin, feel free to fork it and tweak it to make it work, I'm curious about your approach, which is very smart. I didn't know about connect().
#avicarpio give it a go on your application and let us know how it goes :)
I think you could do it like the following, even though I can't think of any easier solution right now (I'm assuming you're using RxJS 7+ with connect() operator):
connect(shared$ => shared$.pipe(
exhaustMap(value => merge(
of(value),
shared$.pipe(debounceTime(1000)),
).pipe(
take(2),
)),
)),
Live demo: https://stackblitz.com/edit/rxjs-qwoesj?devtoolsheight=60&file=index.ts
connect() will share the source Observable and lets you reuse it in its project function multiple times. I'm using it only to use the source Observable inside another chain.
exhaustMap() will ignore all next notifications until its inner Observable completes. In this case the inner Observable will immediately reemit the current value (of(value)) and then use debounceTime(). Any subsequent emission from source is ignored by exhaustMap() because the inner Observable hasn't completed yet but is also passed to debounceTime(). Then take(2) is used to complete the chain after debounceTime() emits and the whole process can repeat when source emits because exhaustMap() won't ignore the next notification (its inner Observable has completed).
Here's a custom operator that (as far s I can tell) does what you're after.
The two key insights here are:
Use connect so that you can subscribe to the source twice, once to ignore emissions with exhaustMap and another to inspect and debounce emissions with switchMap
Create an internal token so that you know when to exit without a debounced emission. (Insures that from your example above, the 4 is still emitted).
function throttleDebounceTime<T>(interval: number): MonoTypeOperatorFunction<T> {
// Use this token's memory address as a nominal token
const resetToken = {};
return connect(s$ => s$.pipe(
exhaustMap(a => s$.pipe(
startWith(resetToken),
switchMap(b => timer(interval).pipe(mapTo(b))),
take(1),
filter<T>(c => c !== resetToken),
startWith(a)
))
));
}
example:
of(1,2,3,4).pipe(
throttleDebounceTime(500)
).subscribe(console.log);
// 1 [...0.5s wait] 4

RxJS: Emit value when delay between source emits too long

I have a stream which emits at 1Hz. Once in a while, there is a delay between the emitted items of some seconds, let's say 10 seconds. I want to create an observable which subscribes to the source, and everytime the delay between the items is too long (e.g. 5s), it shall emit an item of another type. However, when the source emits normal values again, it should emit the source.
-O-O-O-O-O----------O-O-O-O---|---> source
-O-O-O-O-O----X-----O-O-O-O---|---> observable
I thought, that I could use timeoutWith(delay,of(X)) in this case, but this would unsubscribe from the source, loosing the rest of the stream.
When I use switchMap(O => of(O).timeoutWith(delay, of(x)) to have a disposable stream of Os, it does not timeout as the inner observable hasn't been created yet.
Any ideas?
FINAL SOLUTION
This is the solution, which in the end is what I need:
this.sensorChanged
.pipe(
mapTo(SensorEvent.SIGNAL_FOUND),
startWith(PositioningEvent.SIGNAL_UNAVAILABLE),
switchMap(x => concat(of(x), timer(5000).pipe(mapTo(PositioningEvent.SIGNAL_LOST)))),
distinctUntilChanged()
)
The missing link was the startWith() which prevented the switchMap from emission.
Not tested, but this should do the trick:
const result$ = source$.pipe(
switchMap(o => concat(of(o), timer(5000).pipe(mapTo(x))))
);

Asynchronous equivalent to tap() in RxJS?

I have a side effect Observable that's required to be resolved before the main Observable is completed. If it were a synchronous operation, I could use the tap() operator. Is there a clear equivalent for an asynchronous operation?
In the following example, I have to map the inner value back to the outer value I actually want to pipe through. How would I avoid this mapping?
const user = requestUser().pipe(
switchMap(user => {
return requestAndAssignToken(user)
.pipe(
map(token => user)
);
})
);
If I understand correctly, you want to ignore the result of the inner Observable and just have the outer Observable wait for it to emit before moving on.
In that case, I'd suggest the delayWhen operator. It is passed a delayDurationSelector that returns an Observable (the duration Observable). It then behaves like stated in the documentation:
The source value is emitted on the output Observable only when the duration Observable emits a value or completes
For your example, it would look like this:
const user = requestUser().pipe(
delayWhen(user => requestAndAssignToken(user))
);
Here is a simple example

rxjs - is there something like an wait until first Operator?

I have two observables one$ and two$;
I want one$ only to fire when two$ has been fired at least once. This I think is essentially the skipUntil operator.
one$.skipUntil(two$).subscribe()
But let's say one$ has fired while two$ hasn't. I want a behaviour where the stream remembers that one$ has fired and will fire at least once, as soon as two$ did.
Cheers
This looks like you can use the zip() operator that emits the nth item only when all its source Observables have emitted nth item:
const one$ = Observable.from(['a1', 'a2', 'a3'], Scheduler.async);
const two$ = Observable.from(['b1'], Scheduler.async);
Observable.zip(one$, two$, (v1, v2) => v1)
.subscribe(val => console.log(val));
I'm adding Scheduler.async only to simulate asynchronous behavior (for more info see combineLatest behaviour in Rxjs 5?)
This will print to console:
a1
This is ok only if you know the one$ will emit only once.
Eventually you can use combineLatest() that needs all its source Observables to emit at least one item and then emits on any emission where you can ignore two$ with a selector function.
const one$ = Observable.from(['a1', 'a2', 'a3'], Scheduler.async);
const two$ = Observable.from(['b1', 'b2'], Scheduler.async);
Observable.combineLatest(one$, two$.take(1), (v1, v2) => v1)
.subscribe(val => console.log(val));
We know that we only want the first item from two$, the rest can be ignored.
This will print to console:
a1
a2
a3
CombineLatest does what you want. You only need to provide a projector function:
one$.combineLatest(two$, v1 => v1).subscribe(x => console.log(x));
combineLatest waits until all observables have emitted at least one value, and then emits the combination of the last values of each observable. In your case, you only want the value from one observable. That's the reason to use the projector function. This projector function receives each observable value in one param and returns the value that will be emitted.
See an example in CodePen: https://codepen.io/sanzante/pen/vjMKwg
Also, check combineLatest behaviour in RxMarbles.
And the official documentation for combineLatest.
combineLatest is exactly what you want. This operator will not emit an initial value until each observable emits at least one value.
combineLatest(([one$, two$])).subscribe(([one, two]) => {
// do what you want with those values (or value one as you wish)
})

Resources