Asynchronous equivalent to tap() in RxJS? - rxjs

I have a side effect Observable that's required to be resolved before the main Observable is completed. If it were a synchronous operation, I could use the tap() operator. Is there a clear equivalent for an asynchronous operation?
In the following example, I have to map the inner value back to the outer value I actually want to pipe through. How would I avoid this mapping?
const user = requestUser().pipe(
switchMap(user => {
return requestAndAssignToken(user)
.pipe(
map(token => user)
);
})
);

If I understand correctly, you want to ignore the result of the inner Observable and just have the outer Observable wait for it to emit before moving on.
In that case, I'd suggest the delayWhen operator. It is passed a delayDurationSelector that returns an Observable (the duration Observable). It then behaves like stated in the documentation:
The source value is emitted on the output Observable only when the duration Observable emits a value or completes
For your example, it would look like this:
const user = requestUser().pipe(
delayWhen(user => requestAndAssignToken(user))
);
Here is a simple example

Related

Should I use map or switchmap when using angular http module?

I use the following code in an angular app. I used the RxJS map call similar to how array map is used. After reading about RxJS switchmap operator, I'm not sure whether I should use map or switchmap. Should I use switchmap so the observable which is returned from the http call is closed so there is no memory leak?
getPeopleForTypeahead(term: string): Observable<IPersonForTypeahead[]> {
var peopleUrl = `https://localhost:5001/api/peoplesearch?name=${term}`;
return this.http.get<any>(peopleUrl)
.pipe(
map(pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}),
catchError(this.handleError('getPeopleForTypeahead', []))
);
}
peopleAsFlattened = (pla: IPeopleList[]) => {
return pla.map(pl => pl.people.map(p => {
return {
id: p.id,
fullName: p.fullNames[0].firstName + " " + p.fullNames[0].lastName
};
}));
}
map and switchMap have completely different purposes:
map - transform the shape of an emission
switchMap - subscribe to an observable and emit its emissions into the stream
map
Use map when you want transform the shape of each emission. Ex: emit the user name property, instead of the entire user object:
userName$: Observable<string> = this.service.getUser(123).pipe(
map(user => user.name)
);
switchMap
Use switchMap when you want to map an emission to another observable and emit its emissions. Ex: You have an observable of some id and want to emit the resource after fetching it:
user$: Observable<User> = this.userId$.pipe(
switchMap(id => this.service.getUser(id)),
);
When user$ is subscribed to, the user returned from service.getUser(id) is emitted (not the userId string).
switchMap is not interchangeable with the map operator, nor vise versa. Although both of them has to do with mapping (as their names suggest), they have two separate use-cases.
In your particular case, the map operator is the way to go.
When to use switchMap?
You can only use switchMap(cb) when you check all these requirements:
Your callback function, cb, passed into switchMap returns an observable, observable$.
If your cb (callback function) does not return an observable, you should look into operators that don't handle higher-level observables, such as filter and map (what you actually needed); not operators that handle higher-level observables such as concatMap and well, switchMap.
You want to execute your cb sequentially before the next operation down the pipeline (after switchMap) executes.
Maybe you want to run logic inside of cb, and optionally get the return value of cb after executing, so that you can pass it down the pipeline for further processing, for example.
When you want to "discard" what will happen to cb's execution and re-execute cb every time the source observable (the thing that trickles down to switchMap(cb)) emits a new value/notification.
Applying what we hopefully learned, we know that your cb:
pl => {
return this.peopleAsFlattened(pl.peopleList).reduce((p, c) => p.concat(c));
}
returns a plain JavaScript array; not an observable. This takes using switchMap out of the question since it violates the first requirement I made up above.
Hopefully that makes sense. :)
We use switchMap when the source observable is a hot observable. In which case you prefer the behaviour that cancel the succeeding observable when source emits.
In your code, you source is a one-off http call which means it will not emit multiple times and the follow up action is not executing observable but to mutate an array. There is no need to use switchMap

Rxjs observable emit in order

how can i subscribe for example three observables, and to emit when one of them emit new value depend on the order they are, like forkJoin but emit but the order of them is important so if ob3 emit first, i want the value of obv1 to be null, also obv2
and only obv3 that was emitted will have value
for example
forkJoin(obs1,obs2,ob3)
.subscribe([ob1v,ob2v,ob3v]=>{
if (obv1v){ 'do somthing'}
if (obv2v){ 'do somthing'}
if (obv3v){ 'do somthing'}
})
thanks
Maybe combineLatest with an initial value would work for you. combineLatest will emit the latest value from each source observable whenever any of its sources emit. However, it doesn't emit for the first time until all sources have emitted at least one value.
We can use startWith to overcome this by providing an initial value for each source.
combineLatest([
obs1.pipe(startWith(null)),
obs2.pipe(startWith(null)),
obs3.pipe(startWith(null)),
])
.pipe(
skip(1) // prevent emitting initial [null, null, null] value
)
.subscribe(([v1, v2, v3]) => {
// do something here
});
You can see the output in this StackBlitz.
It seems that you want to do different thing for every observable. Maybe you shouldn't gorup them? If you want to group them and do different side effect for every one of them you can do something similar to BizzyBob anwer but instead of having if statements in subscribe use tap() operator for every stream. Something like this:
combineLatest([
obs1.pipe(tap(() => 'do somthing'),
obs2.pipe(tap(() => 'do somthing')),
obs3.pipe(tap(() => 'do somthing')),
])
.subscribe(([v1, v2, v3]) => {
});
Good practise is not to use subscribe method but instead set this stream to some property in component and than use async pipe in the template.

Filter observable array by value of observable property?

I have a stream of emissions conforming to: Observable<Notice[]>. Each Notice has a property, isVisible$ (Observable<boolean>) that determines whether or not it is on screen in this particular moment. I want to filter this array of notices by whether the most recent value of isVisible$ is true. When a new array of notices occurs, I want to begin the process again. I know this entails using switchMap on the higher order observable stream.
Neither types of observable will ever complete, so using operators like toArray() will not work here. Each isVisible$ stream is guaranteed to emit at least once.
I want the output to also be of Observable<Notice[]>, emitting each time the isVisible$ stream of any of the inner observable predicates updates.
What I have so far does emit the proper values, but the inner pipeline just groups notices together and emits them (via scan, in lieu of toArray), it doesn't buffer to the length of from(notices) and then emit (if that makes sense). This makes the end result of the stream is too busy.
notices.pipe(
switchMap(notices => from(notices).pipe(
mergeMap(notice => notice.isVisible$.pipe(
map(isVisible => ({ notice, isVisible }))
)),
filter(({ isVisible }) => isVisible),
map(({ notice }) => notice),
scan((noticesArr, noticeBeingAddedOrRemoved) => {
if (!noticesArr.find(n => n.identifier === noticeBeingAddedOrRemoved.id)) {
noticesArr.push(noticeBeingAddedOrRemoved);
}
return noticesArr;
}, [])
))
);
Here's a reproducible sample of what I'm working with on StackBlitz.
I've changed it to use zip, which will only emit when each of the isVisible$ observables emit. You could also use combineLatest if you want to emit whenever any of the source observables emit, rathern than waiting for all of them.

In what cases take operator is actually used?

I would like to understand why take operator is used in code given below.
private _places = new BehaviorSubject<Place[]>(
// places for initialization
);
get places() {
return this._places.asObservable();
}
addPlace(title: string, description: string, price: number)
{
generatedId: string;
newPlace: Place;
// code to initialize newPlace
return this.http.post<{name: string}>(
'https://ionic-angular-ef2f8.firebaseio.com/offered-places.json',
{...newPlace, id: null})
.pipe(
switchMap(response => {
generatedId = response.name;
return this.places;
}),
take(1),
tap(places => {
newPlace.id = generatedId;
this._places.next(places.concat(newPlace));
})
);
}
post request returns an Observable, and we get a value from it in switchMap operator (note that we do not take a value before calling switchMap). In switchMap we replace the observable with a new observable got from _places, a BehaviourSubject object. After switchMap we use take operator.
Why don't we skip take operator, and use tap straight off? Do we take a value from an observable, because the observable is generated from a subject? Who can explain the use case of take operator in details?
UPDATE
I suspect that the reason I should use take operator after switchMap is that switchMap returns an observable received from an object of type BehaviorSubject which holds emitted values. One can subscribe to such BehaviorSubject object and take the latest emitted value - that's exactly what I did.
httpClient.post() emits one next notification and one complete notification.
However, they're using switchMap to merge another Observable to the chain (this.places). switchMap() won't complete until its source and the inner Observable complete so they're using take(1) to complete the chain after the first emission from this.places which is a BehaviorSubject that doesn't complete until you deliberately call complete() on it.
This is a very similar use-case to using takeUntil() to complete chains. takeUntil() always has to be the last operator in a chain because completing a source Observable to switchMap(), concatMap(), ... doesn't necessarily complete the whole chain. See this for more details https://medium.com/angular-in-depth/rxjs-avoiding-takeuntil-leaks-fb5182d047ef

rxjs async updates to Observable

What's the best way to handle asynchronous updates in the middle of an Observable stream.
Let's say there are 3 observables:
Obs1 (gets data from API) -> pipes to Obs2
Obs2 (transforms data) -> pipes to Obs3
Obs3 (sends transformed data)
(The actual application is more complex, and there's reasons it's not done in a single Observable, this is just a simple example).
That all works well and good if it's a linear synchronous path.
But we also have async messages that will change the output of Obs2.
3 scenarios I'm asking about are:
- we fetch data, and go through Obs1, Obs2 & Obs3
- we get a message to make a change, go through Obs2 & Obs3
- we get a different message to make a change which also needs to apply the change from the previous message, through Obs2 & Obs3
The main problem here is that there are different types of asynchronous messages that will change the outcome of Obs2, but they all need to still know what the previous outcome of Obs2 was (so the any other changes from messages that happened before is still applied)
I have tried using switchMap in Obs2 with a scan in Obs1 like this:
obs1
const obs1$ = obs1$.pipe(
// this returns a function used in the reducer.
map((data) => (prevData) => 'modifiedData',
scan((data, reducer) => reducer(betsMap), {})
)
obs2
const obs2$ = obs1$.pipe(
switchMap(data =>
someChange$.pipe(map(reducer => reducer(data)))
)
)
where someChange$ is a BehaviorSubject applying a change using another reducer function.
This works fine for async message #1 that makes some change.
But when message #2 comes in and a different change is needed, the first change is lost.
the changes that should be in "prevData" in obs1$ is always undefined because it happens before the message is applied.
How can I take the output from obs2$ and apply asynchronous updates to it that remembers what all of the past updates was? (in a way where I can clear all changes if needed)
So if i got the question right, there are two problems that this question tackles:
First: How to cache the last 2 emitted values from stream.
scan definitely is the right way, if this cache logic is needed in more than one place/file, I would go for a custom pipe operator, like the following one
function cachePipe() {
return sourceObservable =>
sourceObservable.pipe(
scan((acc, cur) => {
return acc.length === 2 ? [...acc.slice(1), cur] : [...acc, cur];
}, [])
);
}
cachePipe will always return the latest 2 values passed trough the stream.
...
.pipe(
cachePipe()
)
Second: How to access data from multiple streams at the same time, upon stream event
Here rxjs's combineLatest creation operator might do the trick for you,
combineLatest(API$, async1$ ,async2$,async3$)
.pipe(
// here I have access to an array of the last emitted value of all streams
// and the data can be passed to `Obs2` in your case
)
In the pipe I can chain whatever number of observables, which resolves the second problem.
Note:
combineLatest needs for all streams, inside of it, to emit once, before the operator strats to emit their combined value, one workaround is to use startWith operator with your input streams, another way to do it is by passing the data trough BehaviorSubject-s.
Here is a demo at CodeSandbox , that uses the cachePipe() and startWith strategy to combine the source (Obs1) with the async observables that will change the data.

Resources