RxJS: How to ignore item in stream when outdated - rxjs

I'm trying to subscribe to a "loading" Observable which returns a boolean. The goal is to have a delay for the items in the stream if loading is true, but not if loading is false. It should also be possible to ignore an outdated item, lets assume following case:
In the stream are two items:
Item A with value true, and is passed immediately
Item B with value false, and is passed 200ms later
Item A has value true, because of that it will be delayed by 500ms, Item B will not be delayed. In this situation Item B will arrive before Item A. I'm searching for an solution to completely ignore Item A for the mentioned case.
Here you see my current not-working solution:
this.loading$
.pipe(
distinctUntilChanged(),
delayWhen(loading => timer(loading ? 500 : 0))
)
.subscribe(loading => {
// Do something
});

Just replace delayWhen with switchMap and it should work. switchMap can cancel outdated inner stream. And you probably an one-time observable rather than an timer:
this.loading$
.pipe(
distinctUntilChanged(),
switchMap(
loading =>
of(loading).pipe(delay(loading ? 500 : 0)))
)
.subscribe(loading => {
// Do something
});

Related

Repeat on an BehaviorSubject

I want to reemit the last value of my observable at a fix interval, to I tried
obs.pipe(repeat({delay:1000})).subscribe(x => console.log('Emitted', x));
but it did not work. after looking into this, my observable is in fact a BehaviorSubject.
So my Question is Why does the 1st emits every second
of('Observable').pipe(repeat({ delay: 1000 })).subscribe(x => console.log(x));
but not the this?
var bs = new BehaviorSubject('BehaviorSubject');
bs.pipe(repeat({ delay: 1000 })).subscribe(x => console.log(x));
How to do it with my BehaviorSubject?
Edit
And I would also like to reset my timer when the subject emits a new value.
the solution I found is
var bs = new BehaviorSubject('BehaviorSubject');
bs.pipe(switchMap(x => timer(0,1000).pipe(map => () => x)).subscribe(x => console.log(x));
but it feels ugly.
You can derive an observable from your BehaviorSubject that switchMaps to a timer that emits the received value. Whenever the subject emits, the timer is reset and will emit the latest value:
const bs = new BehaviorSubject('initial value');
const repeated = bs.pipe(
switchMap(val => timer(0, 1000).pipe(
map(() => val)
))
);
Here's a StackBlitz demo.
So my Question is Why does the 1st emits every second, but not the this?
The reason your example code using of as the source works and not the code using the BehaviorSubject can be found in the documentation of the repeat operator:
Returns an Observable that will resubscribe to the source stream when the source stream completes.
The observable created using of completes after it emits the provided value, so it will resubscribe. Since the BehaviorSubject was not completed, it will not resubscribe.

How do I create an observable which emits a value from an ngrx selector, then another value after a delay?

Given an ngrx selector:
store.select('count')
I want to create an observable that will emit values emitted by the selector, then emit another specific value after a delay.
Using concat doesn't work as (I assume) the selector doesn't complete, so the 0 is never emitted:
this.count$ = concat(store.select('count'), of(0).pipe(delay(2000)));
Stackblitz: https://stackblitz.com/edit/so-selector-delay?file=src/app/my-counter/my-counter.component.ts
- click 'Increment' button - Current Count should change to 1 then back to 0 after 2 seconds.
If you want to emit the store.select('count') value, then essentially reset it to 0 after not receiving an emission for 2 seconds, you can use a switchMap to create a source that emits two values:
The emitted count
The "default" value of 0 after 2000ms
The trick here is that the second value (0) will NOT be emitted if another store.select('count') value is received, because switchMap will create a new source and "switch" to that:
this.count$ = store.select('count').pipe(
switchMap(count => concat(
of(count),
of(0).pipe(delay(2000))
))
);
Here's a working StackBlitz demo.
It might even be worth creating a custom operator:
this.count$ = this.store.select('count').pipe(
resetAfterDelay(0, 2000)
);
export function resetAfterDelay<T>(defaultValue: T, delayMs: number) {
return switchMap((value: T) => concat(
of(value),
of(defaultValue).pipe(delay(delayMs))
));
}
StackBlitz
Below is an approach using combineLatest and BehaviorSubject
We are hold a value in a subject and create a timer that emits value 0 after 2s. So we have two Observables, one emits immediately and the other after 2s. We combine this two and the effect is a single observable as desired
valueHolderSubject$ = new BehaviorSubject(0);
...
this.count$ = combineLatest([
store.select("count").pipe(
tap(x => this.valueHolderSubject$.next(x)),
tap(() =>
timer(2000).subscribe({
next: () => {
this.valueHolderSubject$.next(0);
}
})
),
map(() => this.valueHolderSubject$.value),
distinctUntilChanged()
),
this.valueHolderSubject$
]).pipe(map(([, x]) => x));
Demo Here
A per my comments on the answer from BizzyBob, I was getting unreliable results. But I refined it to work using:
this.count$ = merge(
store.select('count'),
store.select('count').pipe(delay(2000), map(_ => 0))
);
See stackblitz: https://stackblitz.com/edit/so-selector-delay-merge-working?file=src/app/my-counter/my-counter.component.ts

VUE with vue-rx / rxjs : How to create a subscription with $watchAsObservable using filter and interval

I need the help in the following vue-rx / RxJs question.
I have to make a subscription in vue-rx that watches a props value, and when it is true then it calls a http request in every 500 ms, and stops it when it is false, or when the returned value is 'COMPLETED'.
I tried something like this:
export default {
props: ['started'],
subscriptions() {
return {
currentHttpState: this.$watchAsObservable('started')
.pipe(pluck('newValue'),filter(value => value === false))
.switchMap(() => interval(500).pipe(switchMap(() => this.callHttpRequest()),distinctUntilChanged())),
Thank you for the help!
I'm not too familiar with vue (or vue-rx), so this may just be half the answer (the RxJS bit).
I'm assuming this.$watchAsObservable('started') with pluck('newValue') is a stream of true and false? (reflecting the value of the started prop)
If so, I would use switchMap to switch between an interval/timer and nothing.
currentHttpState: this.$watchAsObservable('started').pipe(
pluck('newValue'),
map(val => val? timer(0,500) : EMPTY),
switchMap(timer$ => timer$.pipe(
switchMap(_ => this.callHttpRequest()),
takeWhile(result => result.status !== 'COMPLETED')
)
)
that second switchMap will have the effect that if a call takes over 500ms to complete, it will be dropped and you'll never see the results. This also unsubscribed if the takeWhile() condition isn't met - so you'll have to change that to meet your specific requirements.

RxJS - reusing an observable which has completed

Link to code in stackblitz
Is there a way to repeat a completed observable multiple times?
Say I have a button that on click creates an interval observable that emits 10 values, then completes:
fromEvent(button, 'click').pipe(
switchMapTo(interval(500)),
takeWhile(i => i < 10)
)
In the subscription I want to handle both the next and the complete methods:
.subscribe(
i => console.log(i),
() => {},
() => console.log('completed')
);
In this way - the first click will emit one sequence and once it completes, subsequent clicks will not emit again. Is there any way to write this so that all clicks emit the sequence?
I think you should complete the inner observable and not the whole sequence.
(takeWhile should be piped to interval);
You should use switchMap only if you are happy to dismiss the old sequence once a new click event comes in. mergeMap or concatMap otherwise.
const sequence = () => interval(500).pipe(take(10));
fromEvent(button, 'click').pipe(
switchMap(sequence),
)

How to combine a parent and a dependent child observable

There is a continuous stream of event objects which doesn't complete. Each event has bands. By subscribing to events you get an event with several properties, among these a property "bands" which stores an array of bandIds. With these ids you can get each band. (The stream of bands is continuous as well.)
Problem: In the end you'd not only like to have bands, but a complete event object with bandIds and the complete band objects.
// This is what I could come up with myself, but it seems pretty ugly.
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return combineLatest(of(event), ...band$Array);
})),
map(combined => {
const newEvent = combined[0];
combined.forEach((x, i) => {
if (i === 0) return;
newEvent.bands = {...newEvent.bands, ...x};
})
})
)
Question: Please help me find a cleaner way to do this (and I'm not even sure if my attempt produces the intended result).
ACCEPTED ANSWER
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return combineLatest(band$Array).pipe(
map(bandArray => ({bandArray, event}))
);
})
)
ORIGINAL ANSWER
You may want to try something along these lines
getEvents().pipe(
switchMap(event => {
const band$Array = event.bands.map(bandId => getBand(bandId));
return forkJoin(band$Array).pipe(
map(bandArray => ({bandArray, event}))
);
})
)
The Observable returned by this transformation emits an object with 2 properties: bandArray holding the array of bands retrieved with the getBand service and event which is the object emitted by the Observable returned by getEvents.
Consider also that you are using switchMap, which means that as soon as the Observable returned by getEvents emits you are going to switch to the last emission and complete anything which may be on fly at the moment. In other words you can loose some events if the time required to exectue the forkJoin is longer than the time from one emission and the other of getEvents.
If you do not want to loose anything, than you better use mergeMap rather than switchMap.
UPDATED ANSWER - The Band Observable does not complete
In this case I understand that getBand(bandId) returns an Observable which emits first when the back end is queried the first time and then when the band data in the back end changes.
If this is true, then you can consider something like this
getEvents().pipe(
switchMap(event => {
return from(event.bands).pipe(
switchMap(bandId => getBand(bandId)).pipe(
map(bandData => ({event, bandData}))
)
);
})
)
This transformation produces an Observable which emits either any time a new event occurs or any time the data of a band changes.

Resources