I want to implement a progress bar with rxjs,so I need to get the position of mousedown and the position of mousemove after mousedown.here is the test code I write.
useEffect(() => {
const start$ = fromEvent(divRef.current, 'mousedown').pipe(
tap(() => console.log('start')),
map(event => [event.clientX, event.clientY])
)
const move$ = fromEvent(divRef.current, 'mousemove').pipe(
tap(() => console.log('move')),
map(event => [event.clientX, event.clientY])
)
const end$ = fromEvent(divRef.current, 'mouseup')
const drag$ = concat(start$, move$).pipe(takeUntil(end$), repeat())
const subscription = drag$.subscribe(([newX, newY]) => {
setX(newX)
setY(newY)
})
return () => {
subscription.unsubscribe()
}
})
what I think is that when I click down the mouse,I will subscribe the Start$,and then if I move,I will subscribe the move$.But the Phenomenon is quiet different from what I thought.The log in the console just output 'start'.
enter image description here
as you can see from the picture,when I click down and move,I just can subscribe the start$,if I use the concat method wrong.Hope someone can do me a favor.
I assume you want the mouse movements in the order they happen. Here is a small snippet:
import './style.css';
import {
tap,also
fromEvent,
takeUntil,
exhaustMap,
} from 'rxjs';
const mouseDown$ = fromEvent(document, 'mousedown');
const mouseMove$ = fromEvent(document, 'mousemove');
const mouseUp$ = fromEvent(document, 'mouseup');
mouseDown$
.pipe(
tap(console.log),
exhaustMap((start) =>
mouseMove$.pipe(tap(console.log), takeUntil(mouseUp$))
)
)
.subscribe();
Please have a look here: Stackblitz
I have two subjects:
const foo = new Subject();
const bar = new Subject();
And each of them acting different:
const foo$ = foo.pipe(
tap(() => {
console.log('in foo');
})
);
const bar$ = bar.pipe(
tap(() => {
console.log('in bar');
}),
);
When I call bar.next to trigger the bar$ pipeline and it's of course works as expected.
But I want to trigger the foo$ pipeline within the bar$ pipeline like that:
const foo$ = foo.pipe(
tap(() => {
console.log('in foo');
})
);
const bar$ = bar.pipe(
tap(() => {
console.log('in bar');
}),
switchMap(() => foo$)
);
This not working.
I have some limitations:
I don't want to trigger the subject because it trigger every subscribers somewhere in the app.
I can't use foo$.next because it's next don't exist in foo$.
I want to use foo$ in the same pipeline of bar$ using the pipeline.
I try to wrap it in from, switchMap, map, switchMapTo - those not working.
Any idea?
stackblitz
import { Subject, tap } from 'rxjs';
import { switchMap } from 'rxjs/operators';
console.clear();
const foo = new Subject();
const bar = new Subject();
const foo$ = foo.pipe(
tap(() => {
console.log('in foo');
})
);
const bar$ = bar.pipe(
tap(() => {
console.log('in bar');
}),
switchMap(() => foo$)
);
bar$.subscribe();
bar.next(1);
You have an active inner subscription to foo$, but it's not emitting anything because that can only be done with your bar Subject. (Which you say that you don't want to trigger.)
So the next best thing is to combine both your foo subject and the bar$ observable as the source of your foo$ observable. Because we don't know when either observable will emit or complete, we'll use the mergeWith() operator.
import { Subject, tap } from 'rxjs';
import { merge } from 'rxjs/operators';
console.clear();
const foo = new Subject();
const bar = new Subject();
const fooSource$ = foo.pipe(
tap(() => {
console.log('in foo');
})
);
const barSource$ = bar$.pipe(
tap(() => {
// do whatever before bar$ emits to foo$
})
);
const foo$ = fooSource$.pipe(
mergeWith(barSource$),
tap(() => {
// do whatever before foo$ emits its value
})
);
const bar$ = bar.pipe(
tap(() => {
console.log('in bar');
})
);
bar$.subscribe();
bar.next(1);
Note that with mergeWith() you cannot distinguish which observable is triggering the emit. So it's a good idea to have both fooSource$ and barSource$ emit the same value type.
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 };
})
)
}
Is there a way to add one more element after an observable has been subscribed?
observable = timer(1000,2000).take(5)
observable.subscribe()
//Now I want to add one more element to observable....
Maybe a subject is what you are looking for, a subject is both an observable and an observer.
const { Subject, timer, fromEvent } = rxjs;
const { take } = rxjs.operators;
let subject$ = new Subject();
subject$.subscribe(val => { console.log(val); });
timer(1000,2000).pipe(take(5)).subscribe(val => { subject$.next(val); });
fromEvent(document.getElementsByTagName('button')[0], 'click').subscribe(() => {
subject$.next('clicked');
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
<button>Click</button>
You can use a combination of merge and Subjects
const { Subject, timer, merge } = rxjs;
const { take } = rxjs.operators;
let source_1 = timer(1000,2000).pipe(take(5));
let source_2 = new Subject();
let final_source = merge(
source_1,
source_2
);
final_source.subscribe(e => console.log(e));
source_2.next('jabadabadoo!');
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.3/rxjs.umd.min.js"></script>
I created a class which sets up a pausable RxJS Observable using the interval operator:
export class RepeatingServiceCall<T> {
private paused = false;
private observable: Observable<T>;
constructor(serviceCall: () => Observable<T>, delay: number) {
this.observable = interval(delay).pipe(flatMap(() => (!this.paused ? serviceCall() : NEVER)));
}
setPaused(paused: boolean) {
this.paused = paused;
}
getObservable() {
return observable;
}
}
This seems to work fine, but the problem I am trying to solve is that I want the timer to reset when unpaused. So, let's say that the interval time is 10 seconds and 5 seconds after the last time the interval emitted, setPaused(false) is called. In that scenario, I want it to emit immediately and then restart the timer.
Would something like that be an easy thing to add?
If you use timer instead of interval, and set the initial delay to 0, then your interval will fire immediately.
You can use takeUntil operator to prevent the interval to run always, and repeat operator with delay option (or repeatWhen for rxjs <7.0) to restart it whenever you want:
import { Observable, Subject, timer } from 'rxjs';
import { repeat, switchMap, takeUntil } from 'rxjs/operators';
export class RepeatingServiceCall<T> {
readonly observable$: Observable<T>;
private readonly _stop = new Subject<void>();
private readonly _start = new Subject<void>();
constructor(serviceCall: () => Observable<T>, delay: number) {
this.observable$ = timer(0, delay)
.pipe(
switchMap(() => serviceCall()),
takeUntil(this._stop),
// repeatWhen(() => this._start) // for rxjs <7.0
repeat({delay: () => this._start}) // for rxjs >7.0
);
}
start(): void {
this._start.next();
}
stop(): void {
this._stop.next();
}
}
Here is a working StackBlitz example.
P.S.: Getters and setters are working different in typescript. So you do not need classic getter concept, you can just make the attribute public and readonly.
You can achieve the behavior you are describing with the following snippet:
const delay = 1000;
const playing = new BehaviorSubject(false);
const observable = playing.pipe(
switchMap(e => !!e ? interval(delay).pipe(startWith('start')) : never())
);
observable.subscribe(e => console.log(e));
// play:
playing.next(true);
// pause:
playing.next(false);
When the playing Observable emits true, the switchMap operator will return a new interval Observable.
Use the startWith operator to emit an event immediately when unpausing.
If you wish to have the interval start automatically when subscribing to the observable, then simply initialize the BehaviorSubject with true.
StackBlitz Example
Yet another approach with a switchMap:
const { fromEvent, timer } = rxjs;
const { takeUntil, switchMap, startWith } = rxjs.operators;
const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');
start$.pipe(
startWith(void 0), // trigger emission at launch
switchMap(() => timer(0, 1000).pipe(
takeUntil(stop$)
))
).subscribe(console.log);
<script src="https://unpkg.com/rxjs#6.4.0/bundles/rxjs.umd.min.js"></script>
<button id="start">start</button>
<button id="stop">stop</button>
And a simpler one, that merges start and stop Observables to switch off them:
const { fromEvent, merge, timer, NEVER } = rxjs;
const { distinctUntilChanged, switchMap, mapTo, startWith } = rxjs.operators;
const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');
merge(
start$.pipe(mapTo(true), startWith(true)),
stop$.pipe(mapTo(false))
).pipe(
distinctUntilChanged(),
switchMap(paused => paused ? timer(0, 1000) : NEVER)
)
.subscribe(console.log);
<script src="https://unpkg.com/rxjs#6.4.0/bundles/rxjs.umd.min.js"></script>
<button id="start">start</button>
<button id="stop">stop</button>
And another, even wierder approach, using repeat() :
const { fromEvent, timer } = rxjs;
const { take, concatMap, takeUntil, repeat } = rxjs.operators;
const start$ = fromEvent(document.getElementById('start'), 'click');
const stop$ = fromEvent(document.getElementById('stop'), 'click');
start$.pipe(
take(1),
concatMap(()=>timer(0, 1000)),
takeUntil(stop$),
repeat()
).subscribe(console.log);
<script src="https://unpkg.com/rxjs#6.4.0/bundles/rxjs.umd.min.js"></script>
<button id="start">start</button>
<button id="stop">stop</button>
Just wanted to join this party :)
Thanks for #s.alem 's answer, it really helped me.
From official documentation, repeatWhen() is deprecated in RxJs of v7 and will be removed in future version, and repeat() is a replacement of it.
So here's an updated version of #s.alem 's code:
StackBlitz
Basically the change is from
repeatWhen(() => this._start),
to
repeat({ delay: (count) => this._start })
You can abandon the old timer on start and start a new one on start.
const { interval, Subject, fromEvent } = rxjs;
const { takeUntil } = rxjs.operators;
let timer$;
const pause = new Subject();
const obs$ = new Subject();
obs$.subscribe(_ => { console.log('Timer fired') });
function start() {
timer$ = interval(1000);
timer$.pipe(takeUntil(pause)).subscribe(_ => { obs$.next(); });
}
function stop() {
pause.next();
timer$ = undefined;
}
fromEvent(document.getElementById('toggle'), 'click').subscribe(() => {
if (timer$) {
stop();
} else {
start();
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.4.0/rxjs.umd.min.js"></script>
<button id="toggle">Start/Stop</button>
check this code
/**
* it is a simple timer created by via rxjs
* #author KentWood
* email minzojian#hotmail.com
*/
function rxjs_timer(interval, times, tickerCallback, doneCallback, startDelay) {
this.pause = function () {
this.paused = true;
}
this.resume = function () {
this.paused = false;
}
this.stop = function () {
if (this.obs) {
this.obs.complete();
this.obs.unsubscribe();
}
this.obs = null;
}
this.start = function (interval, times, tickerCallback, doneCallback, startDelay) {
this.startDelay = startDelay || 0;
this.interval = interval || 1000;
this.times = times || Number.MAX_VALUE;
this.currentTime = 0;
this.stop();
rxjs.Observable.create((obs) => {
this.obs = obs;
let p = rxjs.timer(this.startDelay, this.interval).pipe(
rxjs.operators.filter(() => (!this.paused)),
rxjs.operators.tap(() => {
if (this.currentTime++ >= this.times) {
this.stop();
}
}),
rxjs.operators.map(()=>(this.currentTime-1))
);
let sub = p.subscribe(val => obs.next(val), err => obs.error(err), () => obs
.complete());
return sub;
}).subscribe(tickerCallback, null, doneCallback);
}
this.start(interval, times, tickerCallback, doneCallback, startDelay);
}
/////////////test/////////////
var mytimer = new rxjs_timer(
1000/*interval*/,
10 /*times*/,
(v) => {logout(`time:${v}`)}/*tick callback*/,
() => {logout('done')}/*complete callback*/,
2000/*start delay*/);
//call mytimer.pause()
//call mytimer.resume()
//call mytimer.stop()
function logout(str){
document.getElementById('log').insertAdjacentHTML( 'afterbegin',`<p>${str}</p>`)
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.1/rxjs.umd.js"></script>
<button onclick="mytimer.pause()"> pause</button>
<button onclick="mytimer.resume()"> resume</button>
<button onclick="mytimer.stop()"> stop</button>
<div id='log'></div>