Reputation: 4082
I found a few implementation of AuthGuard
s that use take(1)
. In my project, I used first()
.
Do both work the same way?
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/first';
import { Observable } from 'rxjs/Observable';
import { Injectable } from '@angular/core';
import { CanActivate, Router, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { AngularFire } from 'angularfire2';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private angularFire: AngularFire, private router: Router) { }
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | boolean {
return this.angularFire.auth.map(
(auth) => {
if (auth) {
this.router.navigate(['/dashboard']);
return false;
} else {
return true;
}
}
).first(); // Just change this to .take(1)
}
}
Upvotes: 257
Views: 289662
Reputation: 4233
There's one really important difference which is not mentioned anywhere.
take(1)
emits 1, completes, unsubscribes
first()
emits 1, completes, but doesn't unsubscribe.
It means that your upstream observable will still be hot after first()
which is probably not expected behavior.
UPD: This referes to RxJS 5.2.0. This issue might be already fixed.
Upvotes: 27
Reputation: 4127
It turns out there's a very important distinction between the two methods: first() will emit an error if the stream completes before a value is emitted. Or, if you've provided a predicate (i.e. first(value => value === 'foo'))
, it will emit an error if the stream completes before a value that passes the predicate is emitted.
take(1), on the other hand, will happily carry on if a value is never emitted from the stream. Here's a simple example:
const subject$ = new Subject();
// logs "no elements in sequence" when the subject completes
subject$.first().subscribe(null, (err) => console.log(err.message));
// never does anything
subject$.take(1).subscribe(console.log);
subject$.complete();
Another example, using a predicate:
const observable$ = of(1, 2, 3);
// logs "no elements in sequence" when the observable completes
observable$
.first((value) => value > 5)
.subscribe(null, (err) => console.log(err.message));
// the above can also be written like this, and will never do
// anything because the filter predicate will never return true
observable$
.filter((value) => value > 5);
.take(1)
.subscribe(console.log);
As a newcomer to RxJS, this behavior was very confusing to me, although it was my own fault because I made some incorrect assumptions. If I had bothered to check the docs, I would have seen that the behavior is clearly documented:
Throws an error if
defaultValue
was not provided and a matching element is not found.
The reason I've run into this so frequently is a fairly common Angular 2 pattern where observables are cleaned up manually during the OnDestroy
lifecycle hook:
class MyComponent implements OnInit, OnDestroy {
private stream$: Subject = someDelayedStream();
private destroy$ = new Subject();
ngOnInit() {
this.stream$
.takeUntil(this.destroy$)
.first()
.subscribe(doSomething);
}
ngOnDestroy() {
this.destroy$.next(true);
}
}
The code looks harmless at first, but problems arise when the component in destroyed before stream$
can emit a value. Because I'm using first()
, an error is thrown when the component is destroyed. I'm usually only subscribing to a stream to get a value that is to be used within the component, so I don't care if the component gets destroyed before the stream emits. Because of this, I've started using take(1)
in almost all places where I would have previously used first()
.
filter(fn).take(1)
is a bit more verbose than first(fn)
, but in most cases I prefer a little more verbosity over handling errors that ultimately have no impact on the application.
Also important to note: The same applies for last()
and takeLast(1)
.
Upvotes: 12
Reputation: 96891
Operators first()
and take(1)
aren't the same.
The first()
operator takes an optional predicate
function and emits an error
notification when no value matched when the source completed.
For example this will emit an error:
import { EMPTY, range } from 'rxjs';
import { first, take } from 'rxjs/operators';
EMPTY.pipe(
first(),
).subscribe(console.log, err => console.log('Error', err));
... as well as this:
range(1, 5).pipe(
first(val => val > 6),
).subscribe(console.log, err => console.log('Error', err));
While this will match the first value emitted:
range(1, 5).pipe(
first(),
).subscribe(console.log, err => console.log('Error', err));
On the other hand take(1)
just takes the first value and completes. No further logic is involved.
range(1, 5).pipe(
take(1),
).subscribe(console.log, err => console.log('Error', err));
Then with empty source Observable it won't emit any error:
EMPTY.pipe(
take(1),
).subscribe(console.log, err => console.log('Error', err));
Jan 2019: Updated for RxJS 6
Upvotes: 341
Reputation: 5364
Here are three Observables A
, B
, and C
with marble diagrams to explore the difference between first
, take
, and single
operators:
* Legend:
--o--
value
----!
error
----|
completion
Play with it at https://thinkrx.io/rxjs/first-vs-take-vs-single/ .
Already having all the answers, I wanted to add a more visual explanation
Hope it helps someone
Upvotes: 63
Reputation: 145880
first()
if:If there are zero emissions and you are not explicitly handling it (with catchError
) then that error will get propagated up, possibly cause an unexpected problem somewhere else and can be quite tricky to track down - especially if it's coming from an end user.
You're safer off using take(1)
for the most part provided that:
take(1)
not emitting anything if the source completes without an emission.first(x => x > 10)
)Note: You can use a predicate with take(1)
like this: .pipe( filter(x => x > 10), take(1) )
. There is no error with this if nothing is ever greater than 10.
single()
If you want to be even stricter, and disallow two emissions you can use single()
which errors if there are zero or 2+ emissions. Again you'd need to handle errors in that case.
Tip: Single
can occasionally be useful if you want to ensure your observable chain isn't doing extra work like calling an http service twice and emitting two observables. Adding single
to the end of the pipe will let you know if you made such a mistake. I'm using it in a 'task runner' where you pass in a task observable that should only emit one value, so I pass the response through single(), catchError()
to guarantee good behavior.
first()
instead of take(1)
?aka. How can first
potentially cause more errors?
If you have an observable that takes something from a service and then pipes it through first()
you should be fine most of the time. But if someone comes along to disable the service for whatever reason - and changes it to emit of(null)
or NEVER
then any downstream first()
operators would start throwing errors.
Now I realize that might be exactly what you want - hence why this is just a tip. The operator first
appealed to me because it sounded slightly less 'clumsy' than take(1)
but you need to be careful about handling errors if there's ever a chance of the source not emitting. Will entirely depend on what you're doing though.
Consider also .pipe(defaultIfEmpty(42), first())
if you have a default value that should be used if nothing is emitted. This would of course not raise an error because first
would always receive a value.
Note that defaultIfEmpty
is only triggered if the stream is empty, not if the value of what is emitted is null
.
Upvotes: 93
Reputation: 731
It seems that in RxJS 5.2.0 the .first()
operator has a bug,
Because of that bug .take(1)
and .first()
can behave quite different if you are using them with switchMap
:
With take(1)
you will get behavior as expected:
var x = Rx.Observable.interval(1000)
.do( x=> console.log("One"))
.take(1)
.switchMap(x => Rx.Observable.interval(1000))
.do( x=> console.log("Two"))
.subscribe((x) => {})
// In the console you will see:
// One
// Two
// Two
// Two
// Two
// etc...
But with .first()
you will get wrong behavior:
var x = Rx.Observable.interval(1000)
.do( x=> console.log("One"))
.first()
.switchMap(x => Rx.Observable.interval(1000))
.do( x=> console.log("Two"))
.subscribe((x) => {})
// In console you will see:
// One
// One
// Two
// One
// Two
// One
// etc...
Here's a link to codepen
Upvotes: 13