84

I have a angular2 active guard which handle if the user is not logged in, redirect it to login page:

import { Injectable } from  "@angular/core";
import { CanActivate , ActivatedRouteSnapshot, RouterStateSnapshot, Router} from "@angular/router";
import {Observable} from "rxjs";
import {TokenService} from "./token.service";

@Injectable()
export class AuthenticationGuard implements CanActivate {

    constructor (
        private router : Router,
        private token : TokenService
    ) { }

    /**
     * Check if the user is logged in before calling http
     *
     * @param route
     * @param state
     * @returns {boolean}
     */
    canActivate (
        route : ActivatedRouteSnapshot,
        state : RouterStateSnapshot
    ): Observable<boolean> | Promise<boolean> | boolean {
        if(this.token.isLoggedIn()){
            return true;
        }
        this.router.navigate(['/login'],{ queryParams: { returnUrl: state.url }});
        return;
    }
}

I have to implement it on each route like :

const routes: Routes = [
    { path : '', component: UsersListComponent, canActivate:[AuthenticationGuard] },
    { path : 'add', component : AddComponent, canActivate:[AuthenticationGuard]},
    { path : ':id', component: UserShowComponent },
    { path : 'delete/:id', component : DeleteComponent, canActivate:[AuthenticationGuard] },
    { path : 'ban/:id', component : BanComponent, canActivate:[AuthenticationGuard] },
    { path : 'edit/:id', component : EditComponent, canActivate:[AuthenticationGuard] }
];

Is there any better way to implement canActive option without adding it to each path.

What I want is to add it on main route, and it should apply to all other routes. I have searched alot, but I could not find any useful solution

Thanks

Rahul Kumar
  • 4,500
  • 5
  • 33
  • 43
Nimatullah Razmjo
  • 1,318
  • 1
  • 14
  • 33

3 Answers3

189

You can introduce a componentless parent route and apply the guard there:

const routes: Routes = [
    {path: '', canActivate:[AuthenticationGuard], children: [
      { path : '', component: UsersListComponent },
      { path : 'add', component : AddComponent},
      { path : ':id', component: UserShowComponent },
      { path : 'delete/:id', component : DeleteComponent },
      { path : 'ban/:id', component : BanComponent },
      { path : 'edit/:id', component : EditComponent }
    ]}
];
Günter Zöchbauer
  • 490,478
  • 163
  • 1,733
  • 1,404
  • Glad to hear :) Thanks for the feedback. – Günter Zöchbauer Apr 19 '17 at 06:44
  • 11
    @GünterZöchbauer I always read your advice. Thank you very much!! But in my case this does not work. CanActive does not work in routing between children. I use canActivateChild. It works. – Smiranin Feb 13 '18 at 14:06
  • Glad to hear you find my answers helpful :) and thanks for mentioning [canActivateChild](https://angular.io/api/router/CanActivateChild) – Günter Zöchbauer Feb 13 '18 at 14:08
  • @GünterZöchbauer This only seems to work for direct children of the componentless guarded parent. This doesn't seem to guard for routes further down in the hierarchy (ie. children routes of children). Is there a way to do that? – Jun Kang Apr 19 '18 at 21:00
  • 3
    I'm not sure. You could try `canActivateChild` instead of `canActivate`. Haven't used that myself yet. – Günter Zöchbauer Apr 20 '18 at 02:45
  • 3
    Yup, `canActivateChild` is a better approach for the given scenario to envelope all child routes. And the parent itself can guarded using `canActivate` or `canLoad` depending on when its loaded in the app. Also, the code snippet will give console errors in v6.0 as Angular will expect a component or child declaration in the route config. – ashish.gd Sep 27 '18 at 18:48
  • canActivateChild is the best approach – Sree Oct 01 '19 at 04:48
  • @Sree You are right. I don't think that existed when I posted the answer. – Günter Zöchbauer Oct 01 '19 at 04:51
  • @GünterZöchbauer agreed. sorry didnt pay attention to when was it answered – Sree Oct 01 '19 at 04:53
  • The approach you show causes **every** route to be secure. In practice, we might have a root page that's open for visitors (e.g. landing page, registration page or login page). I resolved it by having three paths: "" (HomeComponent), "/feature" (also HomeComponent) and "/feature" with children (none of which has empty path, contrary to your example) and only the latter (childreny path) has the router guard. What do you think of that? Am I creating a future problem that I'm not aware of now or is that an acceptable approach? – Konrad Viltersten Dec 15 '19 at 13:46
  • That's just one example. You can structure ir differently and have different guards for different routs or groups of routes. – Günter Zöchbauer Dec 15 '19 at 18:49
14

You can also subscribe to the router's route changes in your app.component's ngOnInit function and check authentication from there e.g.

    this.router.events.subscribe(event => {
        if (event instanceof NavigationStart && !this.token.isLoggedIn()) {
            this.router.navigate(['/login'],{ queryParams: { returnUrl: state.url}}); 
        }
    });

I prefer this way of doing any kind of app wide check(s) when a route changes.

Alan Smith
  • 867
  • 2
  • 17
  • 22
3

I think you should implement "child routing" which allow you to have a parent (with a path "admin" for example) and his childs.

Then you can apply a canactivate to the parent which will automatically restrict the access to all his child. For example if I want to access "admin/home" I'll need to go throught "admin" which is protectected by canActivate. You can even define a parent with an empty path "" if you want

Simrugh
  • 152
  • 7