RxJS takeWhile but include the last value
Solution 1
Since RxJS 6.4.0 this is now possible with takeWhile(predicate, true)
.
There's already an opened PR that adds an optional inclusive
parameter to takeWhile
: https://github.com/ReactiveX/rxjs/pull/4115
There're at least two possible workarounds:
-
using
concatMap()
:of('red', 'blue', 'green', 'orange').pipe( concatMap(color => { if (color === 'green') { return of(color, null); } return of(color); }), takeWhile(color => color), )
-
Using
multicast()
:of('red', 'blue', 'green', 'orange').pipe( multicast( () => new ReplaySubject(1), subject => subject.pipe( takeWhile((c) => c !== 'green'), concat(subject.take(1), ) ), )
I've been using this operator as well so I made it to my own set of additional RxJS 5 operators: https://github.com/martinsik/rxjs-extra#takewhileinclusive
This operator has been also discussed in this RxJS 5 issue: https://github.com/ReactiveX/rxjs/issues/2420
Jan 2019: Updated for RxJS 6
Solution 2
UPDATE March 2019, rsjx
version 6.4.0
: takeWhile
finally have an optional inclusive
parameter that allows to keep the first element that breaks the condition. So now the solution would be simply to pass true as the second argument of takeWhile
:
import { takeWhile } from 'rxjs/operators';
import { from } from 'rxjs';
const cutOff = 4.5
const example = from([2, 3, 4, 5, 6])
.pipe(takeWhile(v => v < cutOff, true ))
const subscribe = example.subscribe(val =>
console.log('inclusive:', val)
);
outputs:
inclusive: 2
inclusive: 3
inclusive: 4
inclusive: 5
Live here:
https://stackblitz.com/edit/typescript-m7zjkr?embed=1&file=index.ts
Notice that 5 is the first element that breaks the condition. Notice that endWith
is not really a solution when you have dynamical conditions like v < cutOff
and you don't know what will be your last element.
Thanks @martin for pointing out the existence of this pull request.
Solution 3
You can use endWith(value)
which (unlike a lot of RxJS code)
is very nicely self documenting.
const example = source.pipe(
takeWhile(val => val != 4),
endWith(4));
PS. Also note that takeUntil
doesn't take a predicate, so if you were trying to use that operator to solve this problem you can't. It's a whole different method signature.
Official docs: https://rxjs-dev.firebaseapp.com/api/operators/endWith
https://stackblitz.com/edit/typescript-pvuawt
Solution 4
If your comparison is such that you know exactly what is the last element (like for !==
), you can re-add it yourself:
Rx.Observable.from([2, 3, 4, 5, 6])
.takeWhile((v) => v !== 4)
.concat(Rx.Observable.of(4))
.subscribe(console.log)
Solution 5
I came across the same problem, i needed the last element to be included so i chose to keep a reference to the subscription and unsubscribe within the onNext
callback when the condition was met. Using your example code it would be:
const subscription = Observable.of('red', 'blue', 'green', 'orange')
.subscribe(color => {
// Do something with the value here
if (color === 'green') {
subscription.unsubscribe()
}
})
This worked for me because it also caused the observable source to stop emitting which is what i needed in my scenario.
I realize that i'm not using takeWhile
operator but the the main goal is achieved and without any workarounds or extra code.
I'm not a fan of forcing things to work in a way that they were not designed to.
The disadvantages of this are:
- If there are any other observers subscribed, the source will keep emitting.
- The
onCompleted
does not get called for some reason if the last observer unsubscribes, but i checked that the source in fact stops emitting.
Fang-Pen Lin
Master Generalist Software Engineer, Senior Software Engineer at Niantic, opinions are my own. My blog Fang's coding note Currently writing a book Rough Road, a guide from zero to master for building software engineer career. You know you want to be a topnotch software engineer, but it's never easy to get there. From time to time, you feel lost and don't know what to do. This is what you are looking for, a guide from ground up to top level based on twenty years intensive programming experience.
Updated on June 13, 2022Comments
-
Fang-Pen Lin almost 2 years
I have a RxJS5 pipeline looks like this
Rx.Observable.from([2, 3, 4, 5, 6]) .takeWhile((v) => { v !== 4 })
I want to keep the subscription until I see 4, but I want to last element 4 also to be included in the result. So the example above should be
2, 3, 4
However, according to official document,
takeWhile
operator is not inclusive. Which means when it encounters the element which doesn't match predicate we gave, it completes the stream immediately without the last element. As a result, the above code will actually output2, 3
So my question is, what's the easiest way I can achieve
takeWhile
but also emit the last element with RxJS?