Reputation: 23322
From the Angular documentation on canActivate
, it seems you can only use canActivate
guards to allow proceeding to a route if the canActivate
function ultimately returns true
.
Is there some way to say, "only proceed to this route if the canActivate
class evaluates to false
" ?
For example, to not allow logged in users to visit the log in page, I tried this but it did not work:
export const routes: Route[] = [
{ path: 'log-in', component: LoginComponent, canActivate: [ !UserLoggedInGuard ] },
I got this error in the console:
ERROR Error: Uncaught (in promise): Error: StaticInjectorError[false]:
StaticInjectorError[false]:
NullInjectorError: No provider for false!
Error: StaticInjectorError[false]:
StaticInjectorError[false]:
NullInjectorError: No provider for false!
Upvotes: 21
Views: 9432
Reputation: 41
Put data in guards
{
path: '',
redirectTo: 'home',
pathMatch: 'full'
},
{
path: 'home',
loadChildren: () => import('./modules/home/home.module').then(m => m.HomeModule),
canActivate: [isAuthenticatedGuard],
},
{
path: 'login',
loadComponent: () => import('./modules/login/login.component').then(m => m.LoginComponent),
canActivate: [isAuthenticatedGuard],
data: { disableAccess: true }
},
{
path: 'register',
loadComponent: () => import('./modules/register/register.component').then(m => m.RegisterComponent),
canActivate: [isAuthenticatedGuard],
data: { disableAccess: true }
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
const url: string = state.url;
const result = this.checkLogin(url);
const { disableAccess = false } = route.data;
const redirectMessage =
"User is not logged - This routing guard prevents redirection to any routes that needs logging."; // TODO: Move it out
if (result) {
if (disableAccess) {
console.log(redirectMessage);
this.router.navigate(['home/main',]); //TODO: redirect to logout, and prepare application for logout
}
} else {
console.log(redirectMessage);
if (!disableAccess) {
this.router.navigate(['login',]); //TODO: redirect to logout, and prepare application for logout
}
}
return disableAccess ? !result : result;
}
And then just manipulate with it like this, or any other way
First: solution to create new guard and reuse canActivate from opposite guard is obviously bad. There can be redirects which can be triggered from another guard, and in general idea to inject guard to another guard - not fine.
Will glad to see your comments if you have any words for me.
Upvotes: 0
Reputation: 1371
I added a data.auth: boolean
option to all of my routes (except the ones that should be accessible either way), like so:
const routes: Routes = [
{
path: '', // all
component: HomeComponent,
},
{
path: 'authenticate', // not authenticated
component: AuthComponent,
data: { auth: false },
canActivate: [AuthGuard],
},
{
path: 'account', // authenticated
component: AccountComponent,
data: { auth: true },
canActivate: [AuthGuard],
},
]
And then in my auth guard, I check for this data property and act accordingly:
export class AuthGuard implements CanActivate {
constructor(private readonly supabase: SupabaseService) {}
canActivate(
route: ActivatedRouteSnapshot,
state: RouterStateSnapshot,
): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
return route.data['auth'] ? !!this.supabase.session : !this.supabase.session
}
}
Upvotes: 0
Reputation: 465
I had a similar problem - wanted to make a login page that was only accessible if not authenticated and a dashboard only accessible if you are authenticated (and redirect user to appropriate one automatically). I solved it by making the guard itself login+route sensitive:
The Routes:
const appRoutes: Routes = [
{ path: 'login', component: LoginComponent, canActivate: [AuthGuard] },
{ path: 'dashboard', component: DashboardComponent, canActivate: [AuthGuard] },
The Guard:
export class AuthGuard implements CanActivate {
private login: UrlTree;
private dash: UrlTree;
constructor(private authSvc: AuthenticationService, private router: Router ) {
this.login = this.router.parseUrl('login');
this.dash = this.router.parseUrl('dashboard');
}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | UrlTree {
if (this.authSvc.isSignedIn()) {
if (route.routeConfig.path === 'login') {
return this.dash;
} else {
return true;
}
} else {
if (route.routeConfig.path === 'login') {
return true;
} else {
return this.login;
}
}
}
}
Upvotes: 4
Reputation: 28434
The interesting thing in your question is the formulation:
Is there some way to say, "only proceed to this route if the canActivate class evaluates to false" ?
And how you expressed the "intuitive" solution:
{ path: 'log-in', component: LoginComponent, canActivate: [ !UserLoggedInGuard ] },
Which basically says, you need to negate
the result of UserLoggedInGuard@canActivate
Lets consider the following implementation of the UserLoggedInGuard
:
@Injectable()
export class UserLoggedInGuard implements CanActivate {
constructor(private _authService: AuthService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return this._authService.isLoggedIn();
}
}
Next, lets look at the solution proposed by @Mike
@Injectable()
export class NegateUserLoggedInGuard implements CanActivate {
constructor(private _authService: AuthService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return !this._authService.isLoggedIn();
}
}
Now, the approach is ok, but is tightly coupled to the (internal) implementation of UserLoggedInGuard
. If for some reason the implementation of UserLoggedInGuard@canActivate
changes, NegateUserLoggedInGuard
will break.
How can we avoid that? Simple, abuse dependency injection:
@Injectable()
export class NegateUserLoggedInGuard implements CanActivate {
constructor(private _userLoggedInGuard: UserLoggedInGuard) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
return !this._userLoggedInGuard.canActivate(route,state);
}
}
Now this is doing exactly what you expressed with
canActivate: [ !UserLoggedInGuard ]
And the best part:
UserLoggedInGuard
Guard
classUpvotes: 25
Reputation: 4821
Thinking about your problem, one solution could be to implement a route guard that does the logic in reverse.
import { MyService } from "./myservice.service";
import { CanActivate, RouterStateSnapshot, ActivatedRouteSnapshot } from "@angular/router";
import { Injectable } from "@angular/core";
@Injectable()
export class MyGuard implements CanActivate {
constructor(private myService: MyService) {}
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.myService.isNotLoggedIn(); //if user not logged in this is true
}
}
Upvotes: 2