Search code examples
angulartypescriptangular-routerangular-router-guards

How to make all non-child routes guarded?


I have created a routing module in my angular 6 application (app.routing.ts). I have added multiple routes to my application, but haven't used the child directive anywhere for my routes (bad practice yes, but now I have added dynamic routes as well on another part of my application and don't want to change the entire structure, as it would take a very long time to do so).

I wanted to know if there was a way I could globally use canActivate: [AuthGuardService] (which checks if user is logged in) for all my routes except the root. I have checked out a similar answer here but the guard doesn't seem to work properly and still loads the page. (Maybe it's because I don't have any child routes).

Also, I think that method also may not be the best way because in that case the the Guard (consumed layer) is able to know which layer is consuming it's methods (which imo is not the best approach for the mvc pattern, granted what I have done with the routing module isn't pretty either). Is there a way to achieve this in a cleaner manner?

app.routing.ts

export const routes: Routes = [
  {
    path: 'main',
    component: MainComponent,
    pathMatch: 'full'
  },
  {
    path: 'search/:id',
    component: GlobalSearchComponent
  },
  {
    path: '',
    canActivate: [AuthGuardService],
    component: LoginComponent,
  },
  {
    path: 'reset',
    component: ResetPasswordComponent
  },
  {
    path: 'forgot',
    component: ForgotPasswordComponent
  },
  {
    path: 'knowledge-base/create',
    component: KnowledgeBaseCreateComponent
  },
  {
    path: 'knowledge-base/detail/:id',
    component: KnowledgeBaseDetailComponent
  },
  {
    path: 'knowledge-base/edit/:id',
    component: KnowledgeBaseEditComponent
  },
  {
    path: 'projects/detail/:id',
    component: ProjectDetailComponent
  },
  {
    path: 'projects/create',
    component: ProjectCreateComponent
  },
  {
    path: '**',
    component: NotFoundComponent
  }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

authguard.service

import { Injectable } from '@angular/core';
import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
import { ToastrService } from 'ngx-toastr';
import { TranslatePipe } from 'src/app/pipes/translate/translate.pipe';

/**
* This injector provides the auth-guard service throughout the application.
*/
@Injectable({
  providedIn: 'root'
})

/**
* The auth guard service is used to prevent unauthenticated users from
* accessing restricted routes, it's used in app.routing.ts to protect the home
* page route
*/
export class AuthGuardService {
  /**
  * The constructor initializes the ToastrService & TranslatePipe in the component.
  */
  constructor(private toastr: ToastrService,
    private translate: TranslatePipe,
    private router: Router) { }
  /**
   * This method checks if the user is authorized to access a certain route.
   *
   * @param  route
   * @param  state
   * @return
   */
  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
    if (localStorage.getItem('jwtToken')) {
      return true;
    } else {
      if (this.router.url !== '/') {
        let error = this.translate.transform("generic[responses][error][401]");
        this.toastr.warning(error);
        this.router.navigate(['']);
      } else {
        return true;
      }
    }
  }
}

Solution

  • The clean way to achieve it would be to wrap them in children of one route. You said that you are aware of that but you also mentioned that it would cause you to change the whole structure - which is not the case. You probably thought of using loadChildren which would in fact cause a bigger change. Here's an example of wrapping your knowledge-base route using children

      {
        path: 'knowledge-base',
        canActivate: [AuthGuardService],
        children: [
            {
              path: '',
              redirectTo: 'knowledge-base/create',
              pathMatch: 'full'
            },
            {
              path: 'create',
              component: KnowledgeBaseCreateComponent
            },
            {
              path: 'detail/:id',
              component: KnowledgeBaseDetailComponent
            },
            {
              path: 'edit/:id',
              component: KnowledgeBaseEditComponent
            }
        ]
      }
    

    This is everything you have to change, basically just wrap it in children. You can apply this approach to other routes as well. Or if you want to use RouterGuard only once, you could create something like path: 'main', and wrap everything there