I have an observable stream which emits numbers, I want another stream that emits all 1's that are not closely followed by a 2 (within say 200ms), so for example from this source stream:
(every character is 100ms)
1...12...112...11121...
The result should be:
..1.......1.....11...1.
How would I do that using rxjs#^6.6.7?
Based on #martin's answer:
when a 1 is emitted,
we create a new observable that waits for the next element for 200ms
if no element comes within the 200msec, we emit the 1 and close the subscription
if an element comes and it's a 2, we emit nothing and close the subscription
if an element comes and it's a 1, we emit the original value and close the subscription
https://stackblitz.com/edit/rxjs-ihyznt?devtoolsheight=66&file=index.ts
source
.pipe(
filter(value => value != 2),
mergeMap(value =>
source.pipe(
first(),
filter(v => v != 2),
map(_ => value),
timeout(200),
catchError(_ => of(value))
)
)
)
.subscribe(value => console.log(value));
I would do it like this. Each 1 is wrapped with an inner Observable and delayed by 200ms which might be completed earlier using takeUntil() and thus ignored:
source$
.pipe(
filter(value => value === 1),
mergeMap(value => of(value).pipe(
delay(200),
takeUntil(source$.pipe(
filter(value => value === 2),
)),
)),
)
Related
I want to start with a rxjs from([1,2,3,4,5]) and then map over each and increment by one but then end up with a combined array by the time I subscribe. Ie: [2,3,4,5,6]
here is my attempt
from([1,2,3,4,5]).pipe(
map(item => item + 1),
concatMap(el => of(el))
).subscribe(res => console.log("res", res)) // want to print [2,3,4,5,6]
You're close! You can use the toArray operator.
from([1,2,3,4]).pipe(
map(v => v + 1),
toArray()
).subscribe(console.log);
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
i want to have a stream that emits while the user is typing. It should emit at most once per second, unless the either the before or the after is an empty input box. In other words, it must always emit immediately if the user types (or pastes) the first character, or if he empties his input. I now have this, but it isn't exactly what I'm looking for:
this.chatForm.get('message')
.valueChanges
.pipe(
startWith(this.chatForm.get('message').value),
pairwise(),
throttle(([a, b]) => {
if (!a || !b) {
return timer(0);
}
return timer(1000);
}),
map(([, b]) => b),
withLatestFrom(this.user$)
)
.subscribe(([ message, user ]) => this.updateUserIsTyping(user, !!message));
with this code it will emit immediately for the first two times that the user starts typing and starts throttling it then, and if you time it right, you will not get an emission when the user clears the input.
What do I need to change to make this work?
Here's my approach:
const input$ = this.chatForm.get('message').valuesChanges;
const emptyInput$ = input$.pipe(filter(v => !v));
const src$ = input$.pipe(
observeOn(asyncScheduler),
throttle(() => timer(1000).pipe(takeUntil(emptyInput$))),
/* ... */
).subscribe(/* ... */)
if the user is continuously typing, a single value per 1000ms will be emitted
if the user empties the input, emptyInput$ will emit, meaning that the throttle's inner observable will complete.
observeOn(asyncScheduler) is used because the valuesChanges Subject has 2 subscribers(input$.pipe(...) and the one from takeUntil) and we want to make sure that the throttle's inner obs. is completed before the empty value is passed along.
Without doing this, the empty value will arrive while the throttling is still happening(the observable is still active) and it wouldn't be emitted, since throttle has leading: true and trailing: false by default.
I actually managed to do it by creating two streams and merging them like this:
const stream1 = this.chatForm.get('message')
.valueChanges
.pipe(
throttleTime(1000)
);
const stream2 = this.chatForm.get('message')
.valueChanges
.pipe(
startWith(this.chatForm.get('message').value),
pairwise(),
flatMap(pair => {
return pair.every(x => x) ? EMPTY : of(pair[1]);
})
);
merge(stream1, stream2)
.pipe(
// when both streams emit at the same time we only want one result
debounceTime(0),
withLatestFrom(this.user$)
)
.subscribe(([ message, user ]) => this.updateUserIsTyping(user, !!message))
I'm still curious to learn if there's a single stream solution though
I have the following code:
const source = interval(1000).pipe(
take(3),
finalize(() => console.log('complete')),
shareReplay({ bufferSize: 1, refCount: true}),
);
source.subscribe(x => console.log(x));
source.subscribe(x => console.log(x));
source.subscribe(x => console.log(x));
Now, the complete log called once. When I move the finalize operator to be after the shareReplay, it called three times, which I guess it's per subscriber, but why it works like that?
const source = interval(1000).pipe(
take(3),
shareReplay({ bufferSize: 1, refCount: true}),
finalize(() => console.log('complete')),
);
The finalize operator subscribed to the internal ReplaySubject which complete once, no?
Finalize operator emits when its source observable is complete.
ShareReplay operator creates a new observable for each subscriber, and replay original observable values.
Take operator only completes after finishing to emit the specified number of take times.
So in your first case, Take(3) observable will emit 3 times, then it’s observable is complete and finalized is called once.
In your second case, ReplayShare will create a new observable per subscriber (over all 3 subscriptions) and finalized will be called 3 times, once for each created observable.
I have a question why is this not writing to console the numbers 2,4,6? what is the explanation?
Observable.range(1, 6)
.groupBy(n => n % 2 === 0)
.concatMap(obs => obs)
.subscribe((n) => console.log(n), null, () => console.log('complete concatMap'))
// this is the output
1 -
3 -
5 -
complete concatMap
The basic problem is that you're using concatMap that subscribes to the next Observable only when the previous one completed. groupBy emits two GroupedObservables so it subscribes to the first one and I think before it can subscribe to the second one the chain completes. This means the observer receives the complete notification from the first GroupedObservable and therefore you never see values from the second GroupedObservable (to be honest I'm not 100% sure it really happens like this but that makes sense to be without further investigating you example).
So if you want only the second group you could do:
import { Observable } from 'rxjs';
Observable.range(1, 6)
.groupBy(n => n % 2 === 0)
.filter(o => o.key === true)
.concatMap(obs => obs)
.subscribe((n) => console.log(n), null, () => console.log('complete concatMap'))
See live demo (open console): https://stackblitz.com/edit/rxjs5-sfused
I checked the source code and groupBy completes all groups after receiving the complete notification (which it does after receiving all values from range) and therefore there's never space for concatMap to subscribe to the second Observable.
See this: https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/groupBy.ts#L200-L210
Problem is that groupBy operator emits Subjects for each key.
Concat map subscribes to subsequent Subject only after the first one is completed, i.e. it misses a chance to catch items from subsequent Subject because all of the sub-streams emit values in the same time.
Kudos to: https://blog.angularindepth.com/those-hidden-gotchas-within-rxjs-7d5c57406041
TL;DR:
GroupBy receives subjectSelector as a 4th argument. You can use it to force using ReplaySubject instead of Subject (default).
Observable.range(1, 6)
.groupBy(
n => n % 2 === 0,
null,
null,
() => new ReplaySubject() // <-- Here we go
)
.concatMap(obs => obs)
.subscribe((n) => console.log(n))
Demo on RxViz