I have an rxjs-Observable which I subscribe to. I now have two different necessities to handle within the pipe of the Observable.
First I want to map the output. Second I want to use tap
to trigger a side-effect, but the side-effect must not be triggered on the first emission.
So this obviously does not work, because the skip
is working globally on the pipe:
this.userChangeSubscription = this.userStateService.userState$
.pipe(
map(userState => userState.prop),
skip(1),
tap(() => this.sideEffect())
)
.subscribe();
Is there any way to do this, without subscribing to the Observable twice?
edit : Ok, I now have several options that all seem to be working. Now: which one to choose?
I've seen that there are already working answers, but I think your best bet would be to write your own rxjs operator for this usecase. This results in a clean solution and inside your pipe
function clarifies your intent.
To do so we need to make use of thedefer
observable :
function tapSkipFirst<T>(fn: Function): OperatorFunction<T, T> {
return function(source: Observable<any>) {
return defer(() => {
let skip = true;
return source.pipe(
tap((v?:any) => {
if (!skip) {
fn(v);
}
skip = false;
})
);
});
};
}
We are using the skip
variable to decide whether we run the side effect function or not. At the end of the first run we toggle skip
to be false and therefore run the side effect function for all following runs. We need to use the defer
observable here, because the code inside defer
only gets called on subscription and not on creation. This is important because otherwise all subscriptions would share the same skip
variable.
And now you can easily use the new custom operator like:
this.userChangeSubscription = this.userStateService.userState$
.pipe(
map(userState => userState.prop),
tapSkipFirst(() => this.sideEffect())
)
.subscribe();
Yes it is possible. Example:
const userProp$ = this.userStateService.userState$.pipe(
map(userState => userState.prop),
shareReplay({ bufferSize: 1, refCount: true })
);
Here shareReplay will let you share that resource between multiple subscribers without triggering the whole chain again. Once all the subscribers are done listening, the shareReplay will end and the observable above will be ended.
Then you can subscribe as many times as you want and also isolate the side effect which is a good idea in the first place:
userProp$
.pipe(
skip(1),
tap(() => this.sideEffect())
)
.subscribe();
You can use the switchMap
operator for such use case. It gives you an index
parameter allowing you to perform different actions based on the emission being the first, second, third etc..
In this case, the sideEffect
will not get triggered if the index
equals 0 (the first emission) but will get triggered for any other futures emissions.
I use this operator for triggering side effects or altering the value being emitted based on a condition. This is great because you don't need to split the source Observable into two different Observables having a different pipeline.
this.userChangeSubscription = this.userStateService.userState$
.pipe(
map(userState => userState.prop),
switchMap((value, index) => {
return index > 0
? of(value).pipe(tap(() => this.sideEffect()))
: of(value);
})
)
.subscribe();
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.