If I have the following code:
const subject = new BehaviorSubject<[]>([]);
const observable = subject.asObservable();
subject.next([{color: 'blue'}])
observable.pipe(first()).subscribe(v => console.log(v))
According to the docs:
If called with no arguments, first emits the first value of the source Observable, then completes....
Does this mean that the source observable(the BehaviorSubject in this case) completes and you can no longer use it? As in you can no longer call .next([...]) on it.
I'm trying to understand how can an observable complete if it doesnt have the .complete() method on it?
I was trying to look at the source code of first() which under the covers uses take() and in turn take() uses lift() so I was curious if somehow first operator returns a copy of the source observable(the subject) and completes that.
The source observable is not completing, what it completes is the subscription. You could have multiple subscriptions on your Observable source, in your case one BehaviorSubject.
subject.next([{color: 'blue'}])
subject.next([{color: 'red'}])
const subs1 = observable.pipe(first()).subscribe(v => console.log(v))
const subs2 = observable.subscribe(v => console.log(v))
In the example above you clearly see that the source is not completing, just the subscription.
I have created a Stackblitz if you want to try it: https://stackblitz.com/edit/rxjs-uv6h6i
Hope I got your point!
Cheers :)
Related
I'm learning about forkJoin. I'm trying to wrap my head around how it works compared to creating an observable with of. Please tell me if I have this right:
When I use of, it creates an observable that will emit the value you pass to it right away. So if I have this:
const obs = of('hello');
obs.subscribe(console.log);
...it will log 'hello' as soon as the line where I subscribe to obs executes. Is this correct?
Now if I have this:
const obs1 = httpClient.get(url1);
const obs2 = httpClient.get(url2);
const fjObs = forkJoin({obs1, obs2});
fjObs.subscribe(({obs1, obs2}) => console.log(`obs1=${obs1}, obs2=${obs2}`));
...it WON'T log obs1 and obs2 as soon as the line where I subscribe to fjObs executes. It will log them only when both obs1 and obs2 have completed, which could be a while after I subscribe to fjObs. Is this correct?
And until that happens, fjObs is just an observable that has not yet emitted any values. Is that correct?
Please let me know if my understanding is correct. Thank you.
Yes you are basically correct. Please note that forkJoin accepts an array of observables, so your code should be: const fjObs = forkJoin([obs1, obs2]); as pointed out in the comment, this assumption was wrong
also you could test this:
forkJoin([of('one'), of('two')]).subscribe(console.log);
in this case the console.log will be executed immediatly since both of emits immediatly.
you could also do: forkJoin([of('one'), httpClient.get(url1)]).subscribe(console.log); in this case it will log after the HTTP request is completed.
You should not compare of with forkJoin since those are very different concepts.
of creates an observable and forkJoin combines an array of observables and emits (the last value of each observable) when all have emitted a value completed
From the RxJS documentation I see the following example:
const source = interval(1000);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe(x => console.log(x));
This is close to a code pattern needed for my app but I see a problem. The takeUntil operator subscribes, but as I understand it an Observer has no way to unsubscribe from the source Observable. It has no access to a Subscription object on which it can call unsubscribe().
So if I understand this correctly then once the user clicks the source observable will continue to emit ticks forever to the takeUntil which will consume them and do nothing with them.
Am I reading this correctly? If so is there a generally accepted way to kill the source observable from within the Observer pipe?
What happens with takeUntil is the following.
When the Observable passed to takeUntil as parameter notifies a value, the subscriber of the Observable returned by takeUntil completes and, as a consequence, all the subscriptions created in the pipe chain are unsubscribed one after the other in reverse order.
In simpler words, the unsubscription is performed behind the scene by the RxJs internal mechanisms.
To prove this behavior you can try this code
const source = interval(1000).pipe(
tap({ next: (val) => console.log('source value', val) })
);
const clicks = fromEvent(document, 'click');
const result = source.pipe(takeUntil(clicks));
result.subscribe((x) => console.log(x));
If you run it, you see that the message 'source value', val is printed until the click occurs. After this, no more message is printed on the console, which means that the Observable upstream, i.e. the Observable created by the interval function does not notify any more.
You can try the above code in this stackblitz.
SOME DETAILS ON THE INTERNALS
We can take a look at the internals of the RxJs implementation to see how this unsubscribe behind the scenes works.
Let's start from takeUntil. In its implementation we see a line like this
innerFrom(notifier).subscribe(new OperatorSubscriber(subscriber, () => subscriber.complete(), noop));
which, in essence, says that as soon as the notifier (i.e. the Observable passed to takeUntil as parameter) notifies, the complete method is called on the subscriber.
The invocation of the complete method triggers many things, but eventually it ends up calling the method execTeardown of Subscription which ends up invoking unsubscribe of OperatorSubscriber which itself calls unsubscribe of Subscription.
As we see, the chain is pretty long and complex to follow, but the core message is that the tearDown logic (i.e. the logic which is invoked when an Observable completes, errors or is unsubscribed) calls the unsubscription logic.
Maybe it is useful to look at one more thing, an implementation of a custom operator directly from the RxJs documentation.
In this case, at the end of the definition of the operator, we find this piece of code
// Return the teardown logic. This will be invoked when
// the result errors, completes, or is unsubscribed.
return () => {
subscription.unsubscribe();
// Clean up our timers.
for (const timerID of allTimerIDs) {
clearTimeout(timerID);
}
};
This is the teardown logic for this custom operator and such logic invokes the unsubscribe as well as any other cleanup activity.
I have the following operators:
const prepare = (value$: Observable<string>) =>
value$.pipe(tap((x) => console.log("remove: ", x)), share());
const performTaskA = (removed$: Observable<string>) =>
removed$.pipe(tap((x) => console.log("pathA: ", x)),);
const performTaskB = (removed$: Observable<string>) =>
removed$.pipe(tap((x) => console.log("pathB: ", x)));
and I call them like this:
const prepared$ = value$.pipe(prepare);
const taskADone$ = prepared$.pipe(performTaskA);
const taskBDone$ = prepared$.pipe(performTaskB);
merge(taskADone$, taskBDone$).subscribe();
Due to the share in prepare I would expect 'remove' to be logged only once, however it appears twice.
Why is this not working?
Codesandbox: https://codesandbox.io/s/so-remove-fires-twice-iyk12?file=/src/index.ts
This is happening because your source Observable is of() that just emits one next notification and then complete. Everything in RxJS in synchronous unless you work with time or you intentionally make your code asynchronous (eg. with Promise.resolve or with asyncScheduler).
In your demo, share() receives one next and one complete notification immediately which makes its internal state to reset. It will also unsubscribe from its source Obserable because there are no more observers (the second source taskBDone$ you're merging has not subscribed yet). Then taskBDone$ is merged into the chain and share() creates internally a new instance of Subject and the whole process repeats.
These are the relevant parts in share():
Dispose handler triggered after receiving complete from source https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/share.ts#L120
New Subject created: https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/share.ts#L113
share() resets its state https://github.com/ReactiveX/rxjs/blob/master/src/internal/operators/share.ts#L163
So if your sources are going to be synchronous you should better use shareReplay() (instead of share()) that will just replay the entire sequence of events to every new observer.
Your updated demo: https://stackblitz.com/edit/rxjs-jawajw?devtoolsheight=60
Notice, that in your demo if you used of("TEST").pipe(delay(0)) as your source Observable it would work as you expected because delay(0) would force asynchronous behavior and both source Observables would first subscribe and then in another JavaScript frame would emit their next and complete.
I have an rxjs function using several rxjs operators from which I need to ultimately return an observable. My challenge is detailed below in the sample code with details of what I am trying to do. How can I accomplish this? Do I need a rewrite? Without the change I'm trying to make to access the values from Observable3 everything works as needed. I've tried many things including using withLatestFrom to bring in observable three and also combineLatest but to no luck.
Observable3 = of({obs3Prop1: value1, obs3Prop2: value2})
this.Observable1
.pipe(
switchMap(param1) => {
return this.getCount(param1);
}),
mergeMap((param2: number) =>
this.Observable2
.pipe(
//inside this pipe, return another observable from here using some rxjs operators
//where I need access to param2
//but I also need access to the value from Observable3
)
),
)
It seems like you could use just forkJoin() assuming the all source Observables complete:
mergeMap((param2: number) =>
forkJoin([
Observable3,
this.Observable2,
]).pipe(
mergeMap(([o3params, o2params]) => this.makeCall(o3params.param1, o2params)),
)
),
TLDR: Working example is in the last codeblock of this question. Check out #bryan60 answer for a working example using concat rather than mergeMap.
I'm trying to run a number of remote requests sequentially, but only the first observable is executed.
The number of request vary, so I can't do a dodgy solution where I nest observables within each other.
I'm using the following code:
const observables = [
observable1,
observable2,
...
];
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, undefined, 1)
).subscribe(res => {
console.log('Huzzah!');
})
In the past (rxjs 5.5) Ive used the following:
let o = Observable.from(observables).mergeMap((ob) => {
return ob;
}, null, 1);
o.subscribe(res => {
console.log('Huzzah!');
})
I'm not sure what I'm doing wrong, can anybody shed some light?
An additional request would be to only print 'Huzzah!' once on completion of all requests rather than for each individual Observable.
EDIT:
Removing undefined from my original code will make it work, however there was another issue causing only the first observable to be executed.
I'm using Angular's HttpClient for remote requests. My observable code looked like this:
const observables = [];
// Only the first observable would be executed
observables.push(this.http.get(urla));
observables.push(this.http.get(urlb));
observables.push(this.http.get(urlc));
Adding .pipe(take(1)) to each observable results in each observable being executed:
const observables = [];
// All observables will now be executed
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
The code I ended up using, which executes all observables in sequential order and only triggers Huzzah! once is:
const observables = [];
observables.push(this.http.get(urla).pipe(take(1));
observables.push(this.http.get(urlb).pipe(take(1));
observables.push(this.http.get(urlc).pipe(take(1));
from(observables).pipe(
mergeMap(ob=> {
return ob.pipe(map(res => res));
}, 1),
reduce((all: any, res: any) => all.concat(res), [])
).subscribe(res => {
console.log('Huzzah!');
})
Thanks to #bryan60 for helping me wit this issue.
if these are http requests that complete, I think your bug is caused by a change to the mergeMap signature that removed the result selector. it's hard to be sure without knowing exactly which version you're on as it was there, then removed, then added again, and they're removing it once more for good in v7.
if you want to run them sequentially... this is all you need...
// concat runs input observables sequentially
concat(...observables).subscribe(res => console.log(res))
if you want to wait till they're all done to emit, do this:
concat(...observables).pipe(
// this will gather all responses and emit them all when they're done
reduce((all, res) => all.concat([res]), [])
// if you don't care about the responses, just use last()
).subscribe(allRes => console.log(allRes))
In my personal utility rxjs lib, I always include a concatJoin operator that combines concat and reduce like this.
the only trick is that concat requires observables to complete till it moves on to the next one, but the same is true for mergeMap with concurrent subscriptions set to 1.. so that should be fine. things like http requests are fine, as they complete naturally after one emission.. websockets or subjects or event emitters will behave a bit differently and have to be manually completed, either with operators like first or take or at the source.
If you are not concerned about the sequence of execution and just want 'Huzzah!' to be printed once all the observable has been executed forkJoin can also be used.Try this.
forkJoin(...observables).subscribe(res => console.log('Huzzah');