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))))
);
Related
how can i subscribe for example three observables, and to emit when one of them emit new value depend on the order they are, like forkJoin but emit but the order of them is important so if ob3 emit first, i want the value of obv1 to be null, also obv2
and only obv3 that was emitted will have value
for example
forkJoin(obs1,obs2,ob3)
.subscribe([ob1v,ob2v,ob3v]=>{
if (obv1v){ 'do somthing'}
if (obv2v){ 'do somthing'}
if (obv3v){ 'do somthing'}
})
thanks
Maybe combineLatest with an initial value would work for you. combineLatest will emit the latest value from each source observable whenever any of its sources emit. However, it doesn't emit for the first time until all sources have emitted at least one value.
We can use startWith to overcome this by providing an initial value for each source.
combineLatest([
obs1.pipe(startWith(null)),
obs2.pipe(startWith(null)),
obs3.pipe(startWith(null)),
])
.pipe(
skip(1) // prevent emitting initial [null, null, null] value
)
.subscribe(([v1, v2, v3]) => {
// do something here
});
You can see the output in this StackBlitz.
It seems that you want to do different thing for every observable. Maybe you shouldn't gorup them? If you want to group them and do different side effect for every one of them you can do something similar to BizzyBob anwer but instead of having if statements in subscribe use tap() operator for every stream. Something like this:
combineLatest([
obs1.pipe(tap(() => 'do somthing'),
obs2.pipe(tap(() => 'do somthing')),
obs3.pipe(tap(() => 'do somthing')),
])
.subscribe(([v1, v2, v3]) => {
});
Good practise is not to use subscribe method but instead set this stream to some property in component and than use async pipe in the template.
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
I have a stream of emissions conforming to: Observable<Notice[]>. Each Notice has a property, isVisible$ (Observable<boolean>) that determines whether or not it is on screen in this particular moment. I want to filter this array of notices by whether the most recent value of isVisible$ is true. When a new array of notices occurs, I want to begin the process again. I know this entails using switchMap on the higher order observable stream.
Neither types of observable will ever complete, so using operators like toArray() will not work here. Each isVisible$ stream is guaranteed to emit at least once.
I want the output to also be of Observable<Notice[]>, emitting each time the isVisible$ stream of any of the inner observable predicates updates.
What I have so far does emit the proper values, but the inner pipeline just groups notices together and emits them (via scan, in lieu of toArray), it doesn't buffer to the length of from(notices) and then emit (if that makes sense). This makes the end result of the stream is too busy.
notices.pipe(
switchMap(notices => from(notices).pipe(
mergeMap(notice => notice.isVisible$.pipe(
map(isVisible => ({ notice, isVisible }))
)),
filter(({ isVisible }) => isVisible),
map(({ notice }) => notice),
scan((noticesArr, noticeBeingAddedOrRemoved) => {
if (!noticesArr.find(n => n.identifier === noticeBeingAddedOrRemoved.id)) {
noticesArr.push(noticeBeingAddedOrRemoved);
}
return noticesArr;
}, [])
))
);
Here's a reproducible sample of what I'm working with on StackBlitz.
I've changed it to use zip, which will only emit when each of the isVisible$ observables emit. You could also use combineLatest if you want to emit whenever any of the source observables emit, rathern than waiting for all of them.
I'm making an 'alert' service. When a new alert comes in, I want to show an alert for a duration. During this duration, I don't want to show other alerts that may have come in. I only want to move to the next alert AFTER the current one is done. So essentially I want to emit the value then wait a duration before emitting the next value.
This can be represented like:
const incomingAlerts$ = interval(1000);
const alerts$ = incomingAlerts$.pipe(
concatMap((alert) => alert.pipe(delay(3500)))
);
This is close to what I want, but delay waits BEFORE emitting the value. Is there an operator or possible set up that will emit the value then wait before emitting the next value (if there is one)?
I think this can be achieved with exhaustMap:
incomingAlerts$.pipe(
exhaustMap(
alert => NEVER
.pipe(
startWith(startAlertAction(alert)),
takeUntil(duration),
endWith(stopAlertAction(alert))
)
)
)
With exhaustMap, an new inner observable won't be created unless the current inner observable becomes inactive(e.g completes/emits an error notification). If an inner observable is already active, then the value from the outer observable will be ignored.
What you need to do is to create a stream that emits a value, waits some amount of time, and then completes.
Here's a kinda weird way of doing that. You set a timer and filter out its emission. Now you have a stream that emits nothing and completes in 3500ms. You then startWith your alert. You can let concatMap handle the rest (It will buffer values until the timer stream completes):
const alerts$ = incomingAlerts$.pipe(
concatMap(alert => timer(3500).pipe(
filter(_ => false),
startWith(alert)
))
);
What's the best way to handle asynchronous updates in the middle of an Observable stream.
Let's say there are 3 observables:
Obs1 (gets data from API) -> pipes to Obs2
Obs2 (transforms data) -> pipes to Obs3
Obs3 (sends transformed data)
(The actual application is more complex, and there's reasons it's not done in a single Observable, this is just a simple example).
That all works well and good if it's a linear synchronous path.
But we also have async messages that will change the output of Obs2.
3 scenarios I'm asking about are:
- we fetch data, and go through Obs1, Obs2 & Obs3
- we get a message to make a change, go through Obs2 & Obs3
- we get a different message to make a change which also needs to apply the change from the previous message, through Obs2 & Obs3
The main problem here is that there are different types of asynchronous messages that will change the outcome of Obs2, but they all need to still know what the previous outcome of Obs2 was (so the any other changes from messages that happened before is still applied)
I have tried using switchMap in Obs2 with a scan in Obs1 like this:
obs1
const obs1$ = obs1$.pipe(
// this returns a function used in the reducer.
map((data) => (prevData) => 'modifiedData',
scan((data, reducer) => reducer(betsMap), {})
)
obs2
const obs2$ = obs1$.pipe(
switchMap(data =>
someChange$.pipe(map(reducer => reducer(data)))
)
)
where someChange$ is a BehaviorSubject applying a change using another reducer function.
This works fine for async message #1 that makes some change.
But when message #2 comes in and a different change is needed, the first change is lost.
the changes that should be in "prevData" in obs1$ is always undefined because it happens before the message is applied.
How can I take the output from obs2$ and apply asynchronous updates to it that remembers what all of the past updates was? (in a way where I can clear all changes if needed)
So if i got the question right, there are two problems that this question tackles:
First: How to cache the last 2 emitted values from stream.
scan definitely is the right way, if this cache logic is needed in more than one place/file, I would go for a custom pipe operator, like the following one
function cachePipe() {
return sourceObservable =>
sourceObservable.pipe(
scan((acc, cur) => {
return acc.length === 2 ? [...acc.slice(1), cur] : [...acc, cur];
}, [])
);
}
cachePipe will always return the latest 2 values passed trough the stream.
...
.pipe(
cachePipe()
)
Second: How to access data from multiple streams at the same time, upon stream event
Here rxjs's combineLatest creation operator might do the trick for you,
combineLatest(API$, async1$ ,async2$,async3$)
.pipe(
// here I have access to an array of the last emitted value of all streams
// and the data can be passed to `Obs2` in your case
)
In the pipe I can chain whatever number of observables, which resolves the second problem.
Note:
combineLatest needs for all streams, inside of it, to emit once, before the operator strats to emit their combined value, one workaround is to use startWith operator with your input streams, another way to do it is by passing the data trough BehaviorSubject-s.
Here is a demo at CodeSandbox , that uses the cachePipe() and startWith strategy to combine the source (Obs1) with the async observables that will change the data.