Preconditions:
The ref.getDownload() returns an Observable which only can be subscribed, if the
task.snapshotChanges()-Observable completed.
This code-snippet works:
task.snapshotChanges().subscribe({
complete: () => {
ref.getDownloadURL().subscribe((downloadUrl) => console.log(downloadUrl));
}
});
This code-snippet does NOT work:
concat(
task.snapshotChanges(),
ref.getDownloadURL()
).pipe(
last()
).subscribe((downloadUrl) => console.log(downloadUrl));
getDownloadUrl throws an error (404 file not found), because it seems
ref.getDownloadUrl is subscribed to early.
Why subscribes the ref.getDownloaded()-Observable and does not wait until task.snapshotChanges() completes? The concat-operator should ensure this behaviour.
Or am I wrong?
The function ref.getDownloadURL() is called when the concat(..) Observable is created. See:
const { of, concat } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), fetch2()).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
ref.getDownloadURL() seems to query the database directly when it gets called and not when the Observable it returns gets subscribed to.
You can wrap ref.getDownloadURL() with defer to only execute it when the Observable is subscribed to.
const { of, concat, defer } = rxjs;
const { delay } = rxjs.operators;
const fetch1 = () => { console.log('run fetch1'); return of('from 1').pipe(delay(2000)) }
const fetch2 = () => { console.log('run fetch2'); return of('from 2').pipe(delay(2000)) }
concat(fetch1(), defer(() => fetch2())).subscribe(console.log);
<script src="https://unpkg.com/rxjs/bundles/rxjs.umd.min.js"></script>
Also see my answer here https://stackoverflow.com/a/57671521/9423231
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>
I'm working on a service layer that manages subscriptions.
I provide subject-backed observables to consumers like this:
const subject = new Subject();
_trackedSubjects.push(subject);
return subject.asObservable();
Different consumers may monitor the channel, so there may be several observables attached to each subject.
I'd like to monitor the count of subject.observers and if it ever drops back to 0, do some cleanup in my library.
I have looked at refCount, but this only is available on Observable.
I'd love to find something like:
subject.onObserverCountChange((cur, prev) =>
if(cur === 0 && prev !== 0) { cleanUp(subject) }
)
Is there a way to automatic cleanup like this on a subject?
Instead of using Subject - you should probably describe setup/cleanup logic when creating observable. See the example:
const { Observable } = rxjs; // = require("rxjs")
const { share } = rxjs.operators; // = require("rxjs/operators")
const eventSource$ = Observable.create(o => {
console.log('setup');
let i = 0
const interval = setInterval(
() => o.next(i++),
1000
);
return () => {
console.log('cleanup');
clearInterval(interval);
}
});
const events$ = eventSource$.pipe(share());
const first = events$.subscribe(e => console.log('first: ', e));
const second = events$.subscribe(e => console.log('second: ', e));
setTimeout(() => first.unsubscribe(), 3000);
setTimeout(() => second.unsubscribe(), 5000);
<script src="https://unpkg.com/rxjs#6.2.2/bundles/rxjs.umd.min.js"></script>
I have three subject. like this:
const s1$ = new Subject()
const s2$ = new Subject()
const s3$ = new Subject()
these three subjects call next() emit same value: const fruit = {id: 1, name: apple};
and, I have three methods to handle the logic one to one correspondence of the subjects call next(fruit) method.
method1() {
//called when s1$.next(fruit)
}
method2() {
//called when s2$.next(fruit)
}
method3() {
//called when s3$.next(fruit)
}
I want to implement this:
// here maybe not Observable.merge, it's just a thinking.
Observable.merge(
s1$,
s2$,
s3$
)
.doSomeOperator()
.subscribe(val => {
//val could be s1$ emit, s2$ emit or s3$ emit
//but the val is same, the fruit.
//do some map like s1->method1, s2->method2, s3->method3, so I can omit if...else statement.
const method = this.method1 | this.method2 | this.method3.
method();
})
How can I implement this, thanks.
Use map operator to add a distinguish sources.
export class AppComponent {
s1(val) {
console.log('s1', val);
}
s2(val) {
console.log('s2', val);
}
constructor() {
const s1= new Subject();
const s2= new Subject();
const m1= s1.map(val=> ({val, source:'s1'}));
const m2 = s2.map(val=> ({val, source:'s2'}));
Observable.merge(m1, m2)
.subscribe(({val, source}) => {
this[source](val);
});
s1.next('apple');
s2.next('apple');
}
}
If there are no priority order (given that no matter which Subject emits, you want to have the method called), I would suggest the following:
// here maybe not Observable.merge, it's just a thinking.
Observable.merge(
s1$.map((val) => ({fn: (arg) => this.method1(arg), val})),
s2$.map((val) => ({fn: (arg) => this.method2(arg), val})),
s3$.map((val) => ({fn: (arg) => this.method3(arg), val}))
)
.subscribe({fn, val}=> {
fn(arg)
});
You can also execute them at the map operator. But well, it depends what you are trying to achieve here
When creating an Rx.Subject using Subject.create(observer, observable), the Subject is so lazy. When I try to use subject.onNext without having a subscription, it doesn't pass messages on. If I subject.subscribe() first, I can use onNext immediately after.
Let's say I have an Observer, created like so:
function createObserver(socket) {
return Observer.create(msg => {
socket.send(msg);
}, err => {
console.error(err);
}, () => {
socket.removeAllListeners();
socket.close();
});
}
Then, I create an Observable that accepts messages:
function createObservable(socket) {
return Observable.fromEvent(socket, 'message')
.map(msg => {
// Trim out unnecessary data for subscribers
delete msg.blobs;
// Deep freeze the message
Object.freeze(msg);
return msg;
})
.publish()
.refCount();
}
The subject is created using these two functions.
observer = createObserver(socket);
observable = createObservable(socket);
subject = Subject.create(observer, observable);
With this setup, I'm not able to subject.onNext immediately (even if I don't care about subscribing). Is this by design? What's a good workaround?
These are actually TCP sockets, which is why I haven't relied on the super slick websocket subjects.
The basic solution, caching nexts before subscription with ReplaySubject:
I think all you wanted to do is use a ReplaySubject as your observer.
const { Observable, Subject, ReplaySubject } = Rx;
const replay = new ReplaySubject();
const observable = Observable.create(observer => {
replay.subscribe(observer);
});
const mySubject = Subject.create(replay, observable);
mySubject.onNext(1);
mySubject.onNext(2);
mySubject.onNext(3);
mySubject.subscribe(x => console.log(x));
mySubject.onNext(4);
mySubject.onNext(5);
Results in:
1
2
3
4
5
A socket implementation (example, don't use)
... but if you're looking at doing a Socket implementation, it gets a lot more complicated. Here is a working socket implementation, but I don't recommend you use it. Rather, I'd suggest that you use one of the community supported implementations either in rxjs-dom (if you're an RxJS 4 or lower) or as part of RxJS 5, both of which I've helped work on.
function createSocketSubject(url) {
let replay = new ReplaySubject();
let socket;
const observable = Observable.create(observer => {
socket = new WebSocket(url);
socket.onmessage = (e) => {
observer.onNext(e);
};
socket.onerror = (e) => {
observer.onError(e);
};
socket.onclose = (e) => {
if (e.wasClean) {
observer.onCompleted();
} else {
observer.onError(e);
}
}
let sub;
socket.onopen = () => {
sub = replay.subscribe(x => socket.send(x));
};
return () => {
socket && socket.readyState === 1 && socket.close();
sub && sub.dispose();
}
});
return Subject.create(replay, observable);
}
const socket = createSocketSubject('ws://echo.websocket.org');
socket.onNext('one');
socket.onNext('two');
socket.subscribe(x => console.log('response: ' + x.data));
socket.onNext('three');
socket.onNext('four');
Here's the obligatory JsBin