2

I'm trying to make a role based routing system where app-routing module has multiple modules routing to the same route.

Eg.

const routes: CustomRoutes = [
  { path: 'login', component: LoginFormComponent },
  {
    path: '',
    canActivate: [CanActivateRole],
    data: {
      roles: UserType.ClientRoleType
    }, 
    pathMatch: 'full',
    loadChildren: () => { return ClientModule; },
  },
  {
    path: '',
    canActivate: [CanActivateRole],
    data: {
      roles: UserType.DCRoleType
    }, 
    pathMatch: 'full',
    loadChildren: () => { return DcEmpModule; },
  },
];

The canActivateRole is reading from route.data.roles to figure out if it can route to the module in loadChildren.

Note: I have tried to use the matcher config to route to load seperate modules as shown in this answer or this, however I couldn't find a way to do it since I am using a service. If there is a way to use a service in the matcher that will work.

UPDATE 1: Some have asked for the service that I am using to figure out what module to load so I've created an approximate of what the canActivate looks like.

@Injectable()
export class CanActivateRole implements CanActivate {
  constructor(private auth: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean {
    // If not logged in
    if (this.auth.notLoggedIn() || this.auth.UserType == null) {
      this.router.navigateByUrl('/login');
      return false;
    }

    let routeData = route.data as CustomRouteData;

    if (routeData.roles == undefined) {
      // Is authenticated but no roles specified so allowed through
      return true;
    } 
    else if (Array.isArray(routeData.roles)) {
      routeData.roles.forEach(allowedRole => {
        if (this.auth.allowParentPermission(allowedRole, this.auth.UserType)) {
          return true;
        }
        return false;
      });
    }
    else {
      if (this.auth.allowParentPermission(routeData.roles, this.auth.UserType)) {
        return true;
      }
    }

    return false;
  }
}

An alternative I tried to do was some sort of service that returned a module to load in loadChildren

@Injectable()
export class RouteMatcher {
  constructor(private auth: AuthService) {}

  /**
   * Used to match user roles to route to modules.
   *
   * To use return function in loadChildren key in routes eg.
   * ```
   * {
   *  path: '',
   *  loadChildren: () => return userRoleModuleMatcher(obj);
   * }
   * ```
   *
   * `Obj`: A map of which UserType or list of UserTypes will be matched onto a module. That module will route all of it's children to the original router. Eg.
   * `[[DCRoleType, EmpRoleType], EmpModule]`
   * will match both `DCRoleType` and `EmpRoleType` onto the `EmpModule`
   */
  public userRoleModuleMatcher(
    obj: ReadonlyMap<
      typeof UserType.UserType[] | typeof UserType.UserType,
      any
    >
  ): any {
    obj.forEach((module, allowedRoles) => {
        if (Array.isArray(allowedRoles)) {
            allowedRoles.forEach(role => {
              if (this.auth.allowParentPermission(role, this.auth.UserType)) {
                return module;
              }
            });
          }
          else {
            if (this.auth.allowParentPermission(allowedRoles, this.auth.UserType)) {
              return module;
          }
        }

        // If there are no matching permissions then route to shared module which has Server Error component
        return SharedModule;
    });
  }
}
JudasMoses
  • 378
  • 4
  • 22
  • Can you add your service code in the question? – NeNaD Sep 02 '21 at 11:04
  • Why do you have to use multiple modules on one route? Just specify each route with his own path, guard and module. – C. Eggart Sep 02 '21 at 11:21
  • @C.Eggart I have a very similar use case to the first problem I linked. Specifying an individual route for each module means I will have to create an additional service for deciding what path to route to. – JudasMoses Sep 03 '21 at 00:42
  • I updated my answer @NenadMilosavljevic – JudasMoses Sep 03 '21 at 00:56
  • So you have a module (module A & module B) for each role, and based on the current role you want to conditionally load either module A or B? Is that it? And does it have to be the same path? Or is it allowed they have a different path? – realappie Sep 09 '21 at 12:29
  • 1
    @realappie that's correct. We are trying to achieve this while having the same path. I implemented a solution. Need to write it up as an answer still. – JudasMoses Sep 10 '21 at 06:07

1 Answers1

2

I don't think this is possible as the canActive run after the route matching.

I think making the roles part of the route is the easy solution here,

const routes: CustomRoutes = [
  { path: 'login', component: LoginFormComponent },
  {
    path: 'client',
    canActivate: [CanActivateRole],
    data: {
      roles: UserType.ClientRoleType
    }, 
    loadChildren: () => { return ClientModule; },
  },
  {
    path: 'dc',
    canActivate: [CanActivateRole],
    data: {
      roles: UserType.DCRoleType
    }, 
    loadChildren: () => { return DcEmpModule; },
  },
];

(not actual code, just an idea!)

Sander Elias
  • 754
  • 6
  • 9
  • Yes I agree. Hopefully [this issue](https://github.com/angular/angular/issues/34231) is considered to be added because this really should be a simple implementation. – JudasMoses Sep 06 '21 at 07:53