7

I'm encountering an issue while trying to lazy load module according to the user profile, I defined three default paths (with empty path for each route) and each user has access to a specific module, i'm using a guards in order to detemine the current user profile (actually i'm toggling manually to set the default loaded module by setting const canGo =true)

The expected behavior is that the actual routing configuration should activate the adequate route according to the profile but that's not the case.

export const routes: Routes = [
  {
    path: '',
    loadChildren: 'app/user/user.module#UserModule',
    canActivate: [
      CanActivateUserModuleGuard
    ],
  },
  {
    path: '',
    loadChildren: 'app/admin/admin.module#AdminModule',
    canActivate: [
      CanActivateAdminModuleGuard
    ]
  },
  {
    path: '',
    loadChildren: 'app/moderator/moderator.module#ModeratorModule',
    canActivate: [
      CanActivateModeratorModuleGuard
    ]
  },
  {
    path: '404',
    component: NotFoundComponent
  }
];

NB: below the online issue if interested https://stackblitz.com/edit/angular-vd4oyu?file=app%2Fapp-routing.module.ts

What is the best way to accomplish this requirement?

2
  • If there is an error, can you post the error stack? Commented Mar 21, 2018 at 11:37
  • There is no error, but the issue here the route detection lands by default to the first route path and doesn't continue to the next route though the guards. in the provided example it should redirect to the Admin module. Commented Mar 21, 2018 at 11:53

2 Answers 2

2

You can achieve this too by using a routeFactory.

So you can load different modules on a single route, and don't need to create extra routes and guards.

The example below loads different modules for the route /parent based on permissions provided by an injectable service.

In app-routing.module.ts:

[
  { path: 'parent', loadChildren: () => 
    import('./parent/parent.module').then((m) => 
    m.ParentModule) },
  ...
]

In parent-routing.module.ts:

function routesFactory(permissionService: PermissioService) {
  return [
  {
    path: '',
    loadChildren: () => {
      if (permissionService.role1) {
        return import('../module1/module1.module').then((m) => m.Module1Module);
      } else if (permissionService.role2) {
        return import('../module2/module2.module').then((m) => m.Module2Module);
      } 
    }
  }];
}

@NgModule({
  imports: [RouterModule.forChild([])],
  exports: [RouterModule],
  providers: [
  {
    provide: ROUTES,
    useFactory: routesFactory,
    multi: true,
    deps: [PermissioService]
  }
  ]
})
export class ParentRoutingModule { }
Sign up to request clarification or add additional context in comments.

Comments

1

The router will only try the guard on the first route that it matches. Because of this limitation you will have to try one of two options.

The first is to implement a custom UrlMatcher that will effectively do the work of your guard and then only have it match on the module you want to load. This can be difficult if you need to make use of other services as the UrlMatcher is just a function so you won't get any DI.

The second option is to have a generic route with path '' that has another guard on it that performs the actions of the other three guards and then routes to the appropriate module. This sounds like a bit of a hack, however, the Angular documentation suggests doing just that in the ToH tutorial.

Demo

Guard

export class CanActivateModuleGuard implements CanActivate {

  constructor(private router: Router,
    private userGuard: CanActivateUserModuleGuard,
    private adminGuard: CanActivateAdminModuleGuard,
    private modGuard: CanActivateModeratorModuleGuard) { }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean {
    let canGo = [{
      guard: this.adminGuard,
      url: 'admin',
    }, {
      guard: this.userGuard,
      url: 'user',
    }, {
      guard: this.modGuard,
      url: 'moderator',
    }].some((config) => {
      if (config.guard.canActivate(route, state)) {
        if (state.url !== `/${config.url}`) {
          this.router.navigate([config.url]);
        }
        return true;
      }
    });

    if (!canGo) {
      this.router.navigate(['/404']);
    }

    return canGo;
  }
}

Routes

export const routes: Routes = [
  {
    path: '',
    canActivate: [CanActivateModuleGuard],
    children: [{
      path: 'user',
      loadChildren: 'app/user/user.module#UserModule',
      canActivate: [
        CanActivateUserModuleGuard
      ],
    },
    {
      path: 'admin',
      loadChildren: 'app/admin/admin.module#AdminModule',
      canActivate: [
        CanActivateAdminModuleGuard
      ]
    },
    {
      path: 'moderator',
      loadChildren: 'app/moderator/moderator.module#ModeratorModule',
      canActivate: [
        CanActivateModeratorModuleGuard
      ]
    }],
  },
  {
    path: '404',
    component: NotFoundComponent
  }
];

2 Comments

Thank you @Teddy Sterne, is it possible to define the routes with an empty url so i do not break my routing configuration within each modules local routing.
The routes withing an module should be able to be relative to the root of the modules and so they will not need to know if they are in /admin, /user, or /moderator. Example <a [routerLink]="./sub/route">Link</a>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.