I'm trying to create a route guard in Angular2+
using Observable from the shared service which holds the string value of current user role.
The problem is obviously in shifting my mind from Promises to Observables.
What I made so far is based on heuristics and try & error approach but I hit the wall by killing the browser Solved thanks to danday74
.
With the help of RxJS sequence equvalent to promise.then()? i've translated what i want to do into this chain:
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | boolean {
return this.auth.isRoleAuthenticated(route.data.roles)
.mergeMap((isRoleAuthenticated: boolean) => {
return isRoleAuthenticated ? Observable.of(true) : this.auth.isRole(Roles.DEFAULT_USER);
})
.do((isDefaultUser: boolean) => {
const redirectUrl: string = isDefaultUser ? 'SOMEWHERE' : 'SOMEWHERE_ELSE';
this.router.navigate([redirectUrl]);
})
.map((isDefaultUser: boolean) => {
return false;
});
}
How to stop further propagation of observable chain if isRoleAuthenticated = true
? I need to return that boolean value if such condition is met and make sure .do
operator block is not called after.
Limitation is that boolean value must be returned from canActivate
guard.
The do()
operator works only with next
notifications so if you don't want to process something that comes after .mergeMap
you can filter it out with filter()
:
return this.auth.isRoleAuthenticated(route.data.roles)
.mergeMap((isRoleAuthenticated: boolean) => {
return isRoleAuthenticated ? Observable.of(true) : this.auth.isRole(Roles.DEFAULT_USER);
})
.filter(authenticated => authenticated !== true)
However, it looks like you could just return Observable.empty()
instead of Observable.of(true)
because that will only emit the complete
notification and no next
items so there will be nothing to be passed to do()
:
.mergeMap((isRoleAuthenticated: boolean) => {
return isRoleAuthenticated ? Observable.empty() : this.auth.isRole(Roles.DEFAULT_USER);
})
Whatever you return from the first mergeMap is passed to your second mergeMap so it won't stop further propagation. If you want to stop propagation use filter (although that may cause a hang in this scenario).
You only use mergeMap when you are returning an Observable but there is no need to return an Observable from the 2nd mergeMap. Just do:
.mergeMap((isRoleAuthenticated: boolean) => {
if (isRoleAuthenticated) {
return Observable.of(true)
}
return this.auth.isRole(Roles.DEFAULT_USER)
})
.tap((isDefaultUser: boolean) => {
if (isDefaultUser) {
this.router.navigate(['SOMEWHERE'])
} else {
this.router.navigate(['SOMEWHERE_ELSE'])
}
})
.map((isDefaultUser: boolean) => {
return false
})
Also, you are using RxJs v5 syntax but should be using v6. In v6 the operators - mergeMap, tap, map - are comma separated in a pipe.
Possibly the router navigation is preventing the final return and causing the hang? Comment that out and see if it stops the hang.
Not sure this will fully fix your prob but hopefully some useful insights for 1am
I am assuming that these return Observables:
If they don't you will get problems.
Instead of focusing on stopping the chain you can create an object consisted of results of collected Observables along the way and propagate it further which solves the problem:
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> | boolean {
return this.auth.getRole()
.mergeMap((role: string) => {
return this.auth.isRoleAuthorized(route.data.roles)
.map((authorized: boolean) => ({
role: role,
authorized: authorized
}));
})
.do((markers: { role: string, authorized: boolean }) => {
const redirectUrl: string = markers.role === Roles.DEFAULT_USER ? 'SOMEWHERE' : 'SOMEWHERE_ELSE';
this.router.navigate([redirectUrl]);
})
.map((markers: { role: string, authorized: boolean }) => {
return markers.authorized;
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With