angulartypescriptangular-routingcanactivateangular-guards

How can you use Angular's canActivate to negate the result of a guard?


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!

Solution

  • 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: