Is BehaviorSubject the same as shareReplay(1)? - rxjs

In these two examples, does subject$ behave in exactly the same way?
import { BehaviorSubject, Observable } from 'rxjs';
let source: Observable<number>;
let subject$: Observable<number>;
subject$ = new BehaviorSubject(0);
source.subscribe(x => {
subject$.next(x);
});
import { Observable } from 'rxjs';
import { shareReplay, startWith } from 'rxjs/operators';
let source: Observable<number>;
let subject$: Observable<number>;
subject$ = source.pipe(
startWith(0),
shareReplay(1)
);

The basic difference is that the 0 value may be re-emitted to the upstream when all subscribers unsubscribe and similar subtleties when no subscribers are active.
e.g.:
import { BehaviorSubject, Subject } from 'rxjs';
const source = new Subject<number>();
const subject$ = new BehaviorSubject(0);
source.subscribe((x) => {
subject$.next(x);
});
source.next(1);
source.next(2);
let subscription = subject$.subscribe(console.log);
source.next(3);
source.next(4);
subscription.unsubscribe();
source.next(5);
source.next(6);
subscription = subject$.subscribe(console.log);
source.next(7);
source.next(8);
// Prints: 2 3 4 6 7 8
import { Subject } from 'rxjs';
import { shareReplay, startWith } from 'rxjs/operators';
const source = new Subject<number>();
const subject$ = source.pipe(startWith(0), shareReplay(1));
source.next(1);
source.next(2);
let subscription = subject$.subscribe(console.log);
source.next(3);
source.next(4);
subscription.unsubscribe();
source.next(5);
source.next(6);
subscription = subject$.subscribe(console.log);
source.next(7);
source.next(8);
// Prints 0 3 4 6 7 8

They replay behavior is the same but number of emission replay can be configured with shareReplay but not with BehaviorSubject. The main difference is shareReplay is an operator which can be add into pipe and convert any source stream to replay value and it doesn't replay any value until first emission happen.
BehaviorSubject is a class which only replay one value and need to be instantiated with a default value and thus when subscribe it always return one cached value.

Related

Don't emit until some other observable has emitted, then emit all previous values

I'm looking for a way to buffer values of an observable until some other observable has emitted, but then emit all the previous values. Something like skipUntil, if skipUntil also emitted skipped values as soon as the second observable emitted.
--a--b----c-----d---e--- (source)
-----------1------------- (other1)
------------abc-d---e----(desired output)
You can use bufferWhen:
import { fromEvent, interval } from 'rxjs';
import { bufferWhen } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const buffered = clicks.pipe(bufferWhen(() =>
interval(1000 + Math.random() * 4000)
));
buffered.subscribe(x => console.log(x));
Here's the custom operator I came up with. Not sure if it can be done in a prettier way.
export function bufferUntil(stopBufferSignal: Observable<any>) {
return <T>(source: Observable<T>): Observable<T> => {
return source.pipe(buffer(stopBufferSignal),
take(1),
flatMap(ar => {
const sourceWithNSkipped$: Observable<T> = source.pipe(skip(ar.length));
const bufferedItems$: Observable<T> = from(ar);
return bufferedItems$.pipe(concat(sourceWithNSkipped$))
}));
}
}

How long was an observable delayed for when using debounceTime?

In this example: https://rxviz.com/v/0oqKpbWJ the delay in time from the first interval to when a value is emitted from the debounceTime operator is 4 seconds.
Is there a way to know that/be able to log the window that a debounce has debounced for?
Yes, you need timeInterval operator https://rxjs.dev/api/operators/timeInterval
Put it after the debounceTime
Update:
okay, I got it. You need a custom operator for sure. Try this
import { fromEvent, OperatorFunction } from 'rxjs';
import { debounceTime, tap, map } from 'rxjs/operators';
const clicks = fromEvent(document, 'click');
const result = clicks.pipe(debounceTimeWithIntervalTracking(1000));
result.subscribe(x => console.log(x));
function debounceTimeWithIntervalTracking<T>(time: number): OperatorFunction<T, { value: T, delayedFor: number }> {
let startedTime = new Date().getTime();
let restart = true;
return src$ => src$.pipe(
tap(() => {
if (restart) {
startedTime = new Date().getTime();
}
restart = false;
}),
debounceTime(time),
map(value => {
const delayedFor = new Date().getTime() - startedTime;
restart = true;
return { value, delayedFor };
})
)
}

how to trigger something immediately then debounce

I have an observable bound to event keyUp on an input box.
For each key pressed. I want to console.log 'Do something now'.
And if there's no key pressed for 5 seconds, then I want to console.log 'Do something else'
import { fromEvent } from 'rxjs';
import { debounceTime, map, switchMap } from 'rxjs/operators';
const searchBox = document.getElementById('search');
const keyup$ = fromEvent(searchBox, 'keyup')
keyup$.pipe(
switchMap((i: any) => 'doSomethingNow'), // I use switchMap here because 'doSomethingNow' is an http request in my real code so that on each key pressed, it cancels the previous http request if it was not finished and start the new http request
debounceTime(2000),
map(_ => 'do something else')
)
.subscribe(console.log);
This code only print 'do something after debounce' after 5 seconds but never print 'domethingNow' after each key pressed
You can use the merge operator:
const searchBox = document.getElementById('search');
const keyup$ = fromEvent(searchBox, 'keyup');
const keyupEnd$ = keyup$.pipe(
switchMap(() => debounceTime(500))
);
const result = merge(
keyup$,
keyupEnd$
);
Have you tried something like:
import { fromEvent } from 'rxjs';
import { debounceTime, map, switchMap } from 'rxjs/operators';
var searchBox = document.getElementById('search');
var keyup$ = fromEvent(searchBox , 'keyup')
keyup$.pipe(
switchMap((i: any) => { console.log('do something');})
debounceTime(5000)) // with delay of 5 secs
.subscribe(console.log('do something else'););

Turn observable into subject

We have a function that gets a stream from the backend as observable. However we would like to be able to push to that observable as well to see the changes before those are done in the back-end. To do so I tried giving back a subject instead but the connection is still on going after the unsubscribe.
In other words, in the code below, we would like the console.log(i) not to start before we subscribe to the subject, and finish when we unsubscribe from it :
import { ReplaySubject, Observable, interval } from 'rxjs';
import { tap } from 'rxjs/operators'
function test() {
const obs = interval(1000).pipe(tap(i => console.log(i)));
const subj = new ReplaySubject(1);
obs.subscribe(subj);
return subj;
}
const subject = test();
subject.next('TEST');
const subscription = subject.pipe(
tap(i => console.log('from outside ' + i))
).subscribe()
setTimeout(_ => subscription.unsubscribe(), 5000);
example
You cannot subscribe in test. I guess you want to create an Observable and a Subject and merge those - you would have to return both separately.
return [subject, merge(subject, obs)]
and then
const [subject, obs] = test();
subject.next()
But I would do it by providing subject as a parameter.
import { ReplaySubject, Observable, interval, merge } from 'rxjs';
import { tap } from 'rxjs/operators'
function test(subject) {
return merge(
interval(1000).pipe(tap(i => console.log(i))),
subject
);
}
const subject = new ReplaySubject(1);
const obs = test(subject);
subject.next('TEST');
const subscription = obs.pipe(
tap(i => console.log('from outside ' + i))
).subscribe()
setTimeout(_ => subscription.unsubscribe(), 5000);

concatMap with interval is not working as expected

I'm trying to understand why my code is not working what I'm expecting it to behave.
If you copy and paste the following code in https://stackblitz.com, you will see it wait 4 seconds then it displays 'aaaa' every second instead of 'bbbb' every second. Why?
import { from, of, race, timer, interval } from 'rxjs';
import { groupBy, mergeMap, toArray, map,merge, reduce, concatMap, delay, concat, timeout, catchError, take } from 'rxjs/operators';
const obs$ = interval(4000).pipe(map(() => 'aaaa'));
const obs2$ = interval(1000).pipe(map(() => 'bbbb'));
const result$ = obs$.pipe(concatMap(() => obs2$));
const subscribe = obs$.subscribe(val => console.log(val + ' ' + new Date().toLocaleTimeString()));
import { from, of, race, timer, interval } from 'rxjs';
import { groupBy, mergeMap, toArray, map,merge, reduce, concatMap, delay, concat, timeout, catchError, take } from 'rxjs/operators';
const obs$ = interval(14000).pipe(map(() => 'aaaa'), take(5));
const obs2$ = interval(1000).pipe(map(() => 'bbbb'));
const result$ = obs$.pipe(concatMap(() => obs2$));
const subscribe = result$.subscribe(val => console.log(val + ' ' + new Date().toLocaleTimeString()));

Resources