14

Is there a way to set a "base" canActivate when configuring routes in Angular2? So that all routes are covered by the same base check, and then each route can have a more granular check.

I have an AppModule with routing like this:

@NgModule({
    imports: [
        RouterModule.forRoot([
            {
                path: '',
                component: HomeComponent,
                canActivate: [AuthenticationGuardService],
                data: { roles: [Roles.User] }
            }
        ])
    ],
    exports: [
        RouterModule
    ]
})
export class AppRoutingModule { }

And for the feature module FeatureModule:

@NgModule({
    imports: [
        RouterModule.forChild([
            {
                path: "feature",
                component: FeatureComponent,

                // I'd like to avoid having to do this:
                canActivate: [AuthenticationGuardService],
                data: { roles: [Roles.User] }
            }
        ])
    ],
    exports: [
        RouterModule
    ]
})
export class FeatureRoutingModule { }

I let AuthenticationGuardService check if a user has access to a route by using the roles provided in data.

Can I do something to avoid having to set canActivate and data in all my feature routing modules? I'd like to just configure a "base" canActivate for all routes in this application.

Joel
  • 8,502
  • 11
  • 66
  • 115
  • You can create route object from a class or helper function. That's the most simple (and probably the only available) solution. – Estus Flask Nov 10 '16 at 09:14
  • @estus So you mean that I should do something like: `RouterModule.forChild([helperService.getRoute("feature", FeatureComponent)])`? And then that helper service (or class or method..) always adds the base Guard to the generated route object? – Joel Nov 10 '16 at 09:44
  • 2
    Yes. I would personally go with something like `RouterModule.forChild([new AuthenticatedRoute({ path: ..., component: ... })])` – Estus Flask Nov 10 '16 at 10:06
  • @Joel, what was the verdict? do you mind sharing your AuthenticationGuardService? on further googling, this sounds like a solution? http://stackoverflow.com/questions/40672453/angular2-restrict-all-routes – sawe Jan 12 '17 at 02:59
  • @sawe Haven't really found a solution I'm happy with. The one you link to is ok, but still requires you to put it in all your routing modules. – Joel Jan 12 '17 at 08:10
  • i meant, you could have your main route with {path: '', canActivate: [AuthenticationGuardService], canActivateChild: [AuthenticationChildGuardService], children: [... /*no need for auth guard here, this is where you would add all your other routes as children on the main component-less route*/]} you still need the `data` though – sawe Jan 12 '17 at 18:41
  • on closer inspection, if you have more than 2 levels of hierarchy, my suggestion might/will not work – sawe Jan 13 '17 at 06:18

2 Answers2

12

I've written a solution to add the Guard dynamically for every routes of my app (including those defined by child modules).

I've figured out this solution while reading this Router Docs.

Edit

Here is a living StackBlitz sample.

const routes: Routes = [
  { path: '', redirectTo: 'home', pathMatch: 'full' },
  { path: 'login', component: LoginComponent, data: { skipGuard: true } },
  { path: '403', component: ForbiddenComponent, data: { skipGuard: true } },
  { path: '**', component: NotFoundComponent, data: { skipGuard: true } }
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: []
})
export class AppRoutingModule {
  
  constructor(router: Router) {
    router.config
        .filter(route => !route.data || !route.data.skipGuard)
        .forEach(route => this.addGuard(route));
  }
  
  private addGuard(route: Route): void {
    route.canActivate = route.canActivate ? 
                   [AuthGuard].concat(route.canActivate) : [AuthGuard];
    route.canActivateChild = route.canActivate ? 
                   [AuthGuard].concat(route.canActivate) : [AuthGuard];
  }
}
Jorge Ciombalo
  • 150
  • 1
  • 6
  • 1
    just a quick note that if `route.canActivate` is undefined for a non skipped route, then the updated `route.canActivate` will produce `[AuthGuard, undefined]` using above answer. And its actually very hard to debug and be found as the actual reason for the resulting error. – ar27111994 Jan 13 '19 at 18:13
  • I tried to implement this but I couldn't. The guard is added but I get an error on every navigation. Maybe it's because my guard has dependencies that are resolved differently when injected in `AppRoutingModule`. Do you have a running example on stackblitz or something like that? Thanks. – Marcos Dimitrio Jan 14 '19 at 13:40
  • 1
    @MarcosDimitrio I've just edited the answer to include a StackBlitz demo. – Jorge Ciombalo Jan 18 '19 at 13:19
  • @ar27111994 I've made a fix to the error you have pointed. Check it in the stackblitz link I've included. – Jorge Ciombalo Jan 18 '19 at 13:22
9
const routes: Routes = [
  {
    path: '',
    canActivate: [AuthGuard],
    children: [
      { path: '', component: HomeComponent },
      { path: 'builds', component: BuildsComponent },
      { path: 'files', component: FilesComponent },
      { path: 'publications', component: PublicationsComponent }
    ]
  },
  { path: 'login', component: LoginComponent },
  { path: '**', redirectTo: '' }
];
  • 6
    While working, this is currently not safe because child routes added via `forChild` are added as siblings to the `forRoot` routes thus not within the hierarchy and will not trigger the guard. – Shlomi Assaf Oct 24 '17 at 22:39
  • You should use `canActivateChild` instead of `canActivate` for this to work properly. – cederlof Nov 30 '18 at 12:12