Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

router canActivate not working with Observer or Promise response #9613

Closed
aghou opened this issue Jun 26, 2016 · 21 comments
Closed

router canActivate not working with Observer or Promise response #9613

aghou opened this issue Jun 26, 2016 · 21 comments

Comments

@aghou
Copy link

aghou commented Jun 26, 2016

router canActivate feature not work with Observable response but work with simple boolean response.
see this example :

canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean>{ console.log('canActivate with AclService'); // if i return boolean, canActivate feature work // return true; // but if i return Observable, canActivate feature not work return Observable.create((observer:Subject<boolean>) => { observer.next(true); }); }

haw can i fix this problem ???

[x] bug report
  • Angular version: 2.0.0-rc.3
  • Browser: [all ]
  • Language: [TypeScript ]
@DzmitryShylovich
Copy link
Contributor

Observable.of(true)

@vicb
Copy link
Contributor

vicb commented Jun 26, 2016

Your question sounds like a support request.

Please use the issue tracker only for bugs and feature requests.

Use gitter for support request.

@vicb vicb closed this as completed Jun 26, 2016
@aghou
Copy link
Author

aghou commented Jun 26, 2016

@vicb it's not support request, in the official doc api syntax: canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | boolean; then canActivate returns one of this results : Observable Or a boolean but with the first case Observable , it is not working

@darwin-gautalius
Copy link

I'm having the same problem. Here is my guard:

import { Injectable } from '@angular/core';
import { CanActivate, Router }    from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { FirebaseAuth } from 'angularfire2';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private router: Router, private auth: FirebaseAuth) { }

  canActivate(): Observable<boolean> {
    return this.auth.map(authState => {
      if (!authState) this.router.navigate(['/login']);
      console.log('activate?', !!authState);
      return !!authState;
    });
  }
}

Browser console show activate? true but the page doesn't show. The component attached to the path is not initialized too.

I tried with return Observable.of(true); and return true; and the page shows correctly.

I don't think canActivate support Observable<boolean> only for Observable.of(true).

btw, I use this firebase. (just for additional info)
"angularfire2": "2.0.0-beta.0",
"firebase": "2.4.2",

@jpsantosbh
Copy link

Same here with "@angular/core": "2.0.0-rc.2", "@angular/router": "^3.0.0-alpha.3",

@Denhai
Copy link

Denhai commented Jun 27, 2016

@darwin-gautalius
I think the observable needs to complete. I had more success with .take(1) at the end. That would explain why Observable.of(true) worked.
Try this:

canActivate(): Observable<boolean> {
  return this.auth.map(authState => {
    if (!authState) this.router.navigate(['/login']);
    console.log('activate?', !!authState);
    return !!authState;
  }).take(1)
}

@aghou
Copy link
Author

aghou commented Jun 27, 2016

i juste done to test it with .take(1) it's work fine,
thank's @Denhai

@darwin-gautalius
Copy link

thanks @Denhai. It really works as intended.

@PEsteves8
Copy link

Thanks for this. I guess angular2 just keeps waiting for it to finish. I also tried with .first(), instead of .take(1) and using observer.complete() after next and it worked as well. Not sure if there are better practices or problems with any of the approaches that one should know.

@philipbulley
Copy link

The fact that the Observable needs to complete should be documented, this wasn't obvious to me either.

@awerlang
Copy link
Contributor

I sent a PR fixing this issue. Take a look at #10412.

@crowmagnumb
Copy link

Are there any plans to have a guard properly handle as an Observable? I have a problem that I need to wait for a login to happen before telling the guard true or false. If the user goes straight to a url defined as a route say "/admin" then when the canActivate() is called I don't have the user or its rights yet. So I have to default to false. When the login check is made (which is triggered automatically) and it comes back saying "yes, they have admin rights" I set the observable to true. But by then the router has determined that no it is not accessible so the router component does not show. The user then has to click on the link manually to get to the admin page. Since the canActivate() is now true it works, but annoying that I can't go straight to the admin page by typing in the url.

@alobakov
Copy link

alobakov commented Oct 19, 2016

@crowmagnumb
I would try the following approach:
In the service that determines whether the user is admin, and which you'd be able to access from your canActivate function or guard class method, declare this:

private _isAdmin$ = new ReplaySubject(1); // this ensures that the router can always get the most recent value set, but only once it becomes available at all

get isAdmin$() { return this._isAdmin$.asObservable(); }

In your canActivate() function (or guard class method):

return mySecurityService.isAdmin$.first(); // NOTE: this still returns an observable!

And then, once the security service determines if the user has admin privileges, it would set the value:

this._isAdmin$.next(true); // or false if the user is not an admin

I'm doing something very similar, but in my case user information comes as part of page load from the server. So I have an initial value to provide, therefore I'm using BehaviorSubject instead of ReplaySubject with a capacity of 1. In a sense, these two are not very different, with the exception being that the former requires an initial value in the constructor, while the latter allows no value.
I have also performed a test just now by introducing a delay in my canActivate() method, which has proved that the router can wait for a value from the observable, and once it becomes available - will decide whether to navigate to the respective route based on that value.
Hope this helps!

@hamidhst
Copy link

hamidhst commented Jun 12, 2017

@alobakov Just Perfect!
After wasting a whole day, you saved it, thanks a lot. (I'm using Angular v4.)

@Snesi
Copy link

Snesi commented Jun 27, 2017

@alobakov Thank you!

I was using a BehaviourSubject and for some reason it wasn't working.

@raysuelzer
Copy link

It would be great if there was a way to have it work without requiring the observable to complete. Using a AsyncSubject or BehaviorSubject allows throttling of any Ajax requests made by calling canActivate.

@gang-qiu
Copy link

@Snesi Was also not able to get Behavior subject to work. Replay subject works though. Using angular 5 and the same overall implementation as @alobakov suggested but for BehaviorSubject

@prashuMishra
Copy link

prashuMishra commented Oct 18, 2018

i have the same issue.

app.route.ts.

export const ROUTES: Routes = [

{
path: '',
loadChildren: 'app/login/login.module#LoginModule',
},
{
path: 'home',
loadChildren: 'app/home/home.module#HomeModule',
canActivate: [AuthGuard]
},
{ path: '**', redirectTo: '' },
];

authguard.ts

import { Injectable } from '@angular/core';
import { Router, CanActivate } from '@angular/router';
import { AuthService } from './auth.service';
import { Observable } from 'rxjs/Observable';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(public auth: AuthService, public router: Router) {}
canActivate(): Observable {
console.log('Canactivate Called');
return Observable.of(true).take(1);
}
}

above code should show "Canactivate Called" in console. but is not working.

@RazvanSebastian
Copy link

RazvanSebastian commented Apr 12, 2019

Inside AuthGuard I have the following

   // Guard method
    canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<any> {
     
        return this._authService.checkTokenAvailability().pipe(
            map((response: Response) => {
               // return Observable with true value and let the router and the guard to redirect you to dashboard in my case
                return true
            }),
            catchError((err: Response) => {
               // navigate to login page
                this._router.navigateByUrl('/admin');
                // handle the error by throwing statusText into the console
               return throwError(err.statusText);
            })
        );
    }

And somewhere into the router module you have

const routes: Routes = [
  ...
  {
    path: 'dashboard',
    component: DashboardComponent,
    canActivate: [AuthGuardService]
  }
....
];

@Snesi
Copy link

Snesi commented Apr 13, 2019 via email

@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 14, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests