RxJS: The differences between first(), take(1), and single()
The different ways to get the first element of an Observable
With Observables listening for mousedown
and mouseup
events, implementing drag-and-drop requires knowing when the first mouseup
happens after
a mousedown
event. For this, you need to get the first event in the mouseup
stream for every event coming in the mousedown
.
It turns out, there are more than one ways to do this, and they seemingly do the same. The first()
is the primary candidate for this use-case, but there is
also take(1)
that also returns the first element. Then there is the single()
operator, which works a bit different but is also used to return the
first element in some cases.
rxjs.of(1, 2, 3).pipe(rxjs.operators.first()) // 1
rxjs.of(1, 2, 3).pipe(rxjs.operators.take(1)) // 1
rxjs.of(1).pipe(rxjs.operators.single()) // 1
So, what is the difference between these operators?
first()
vs take(1)
Both of these return the first element in an Observable and they even have the same timing as they both complete the result after emitting the element.
The difference is when there are no elements in the input stream. In this case, first()
throws an Error, while take(1)
closes the Observable
without any elements.
rxjs.of().pipe(rxjs.operators.first()) // EmptyError
rxjs.of().pipe(rxjs.operators.take(1)) // no elements
When you are sure that the input Observable is not empty, it's safer to use first()
as it will report the empty case as an error. On the other hand, if an
empty stream can happen, for example the element for the mouseup
events is removed from the DOM, take(1)
is better.
single()
This operator behaves a bit differently than the other two. It not only throws an Error for an empty stream (just like first()
) but also for one that has
more than 1 element.
This also changes when it completes the result Observable as it needs to wait for the second element (or the end of the input stream) to know that there is indeed only one element coming in the input.
rxjs.of(1).pipe(rxjs.operators.single()) // 1
rxjs.of(1, 2, 3).pipe(rxjs.operators.single()) // Sequence contains more than one element
rxjs.of().pipe(rxjs.operators.single()) // EmptyError
The single()
operator is a safer version of first()
if you want to make sure that only a single element is emitted in the input Observable.
The predicate and defaultValue arguments
The first()
and the single()
operators also support the predicate argument to filter the elements. Apart from this, first()
also supports the
defaultValue that it returns in case of an empty Observable. take(1)
supports neither.
// predicate
rxjs.of(1, 2, 3).pipe(rxjs.operators.first((e) => e % 2 === 0)) // 2
// defaultValue
rxjs.of().pipe(rxjs.operators.first(undefined, 5)) // 5
These are merely syntactic sugar that you can emulate with the filter
and the defaultIfEmpty
operators:
rxjs.of(1, 2, 3).pipe(
rxjs.operators.filter((e) => e % 2 === 0),
rxjs.operators.first(),
) // 2
rxjs.of().pipe(
rxjs.operators.defaultIfEmpty(5),
rxjs.operators.first(),
) // 5
In the case of single()
, providing a filter changes how it reacts to empty streams. If there are no elements in the Observable then it emits an Error.
But if there are elements but they are filtered out, it emits undefined
instead.
Conclusion
The first()
operator emits an Error for an empty Observable, while take(1)
emits nothing.
The single()
operator also emits an Error for the second element in the Observable. This also changes the time it emits its result as it needs to wait
for the end of the stream (or the second element).