3

I'm writing a mock e-commerce app which has the following structure:

  • app
    • auth
      • sign-in-page
      • sign-up-page
      • auth-routing.module.ts
        const routes: Routes = [
          {
            path: 'signup',
            component: SignUpPage,
          },
          {
            path: 'signin',
            component: SignInPage,
          },
        ];
        
      • auth.module.ts
    • admin
      • root-page
      • admin-routing.module.ts
        const routes: Routes = [
          {
            path: 'admin',
            component: RootPage,
            children: [
              // ...                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
            ],
          },
        ];
        
      • admin.modules.ts
    • customer
      • root-page
      • catalog-page
      • check-out-page
      • thank-you-page
      • customer-routing.module.ts
        const routes: Routes = [
          {
            path: 'customer',
            component: RootPage,
            children: [
              { path: 'catalog/:categoryId', component: CatalogPage },
              { path: 'checkout', component: CheckOutPage },
              { path: 'thankyou', component: ThankYouPage },
            ],
          },
        ];
        
      • customer.module.ts
    • page-not-found
    • app-routing.module.ts
      const routes: Routes = [
        { path: '**', component: NotFoundPage },
      ];
      
    • app.module.ts
    • app.component.html
    • app.component.css

The basic workflow is supposed to be as follows:

  1. The user navigates to the root path /.
  2. The application detects that they are not logged in, so it redirects them to /signin.
  3. They enter their credentials and press Sign In.
  4. If the authentication is successful,
    1. If the user is an admin, they get redirected to /admin.
      1. admin-router.module.ts redirects them to some sub-path of /admin.
    2. If the user is a customer, they get redirected to /customer.
      1. customer-router.module.ts redirects them to /customer/catalog/<default category ID>.
      2. They put some products in the shopping cart and proceed to /customer/checkout.
      3. They place the order and get redirected to /customer/thankyou.

What I'm not sure about is how to accomplish the redirection following a successful log-in. Obviously it has to be done in two stages: first to some common path such as / and then either to /customer or to /admin depending on the user's role. The second stage probably needs to be handled by app-routing.module.ts, perhaps using a guard, but I'm not sure exactly how to do it.

EDIT (2021-04-20)

The problem can be summarized as follows:

What I need is a way (preferably declarative) to redirect the application from / to one of the following paths depending on its state:

State Path
Logged out /auth
Logged in as a customer /customer
Logged in as an admin /admin
Ron Inbar
  • 2,044
  • 1
  • 16
  • 26
  • For the second part I think you should better use a `resolver` that will return the data you need. – ShayD Apr 18 '21 at 13:59
  • Also take a look here https://stackoverflow.com/a/38096468/5107490 – ShayD Apr 18 '21 at 14:07
  • 1
    Why don't you plainly router.navigate to either /admin or /customer after the user is authenticated? – c_froehlich Apr 18 '21 at 14:25
  • @c_froehlich Obviously this will work, but it will create undesirable coupling between the sign-in page and the rest of the app. To put it less theoretically: in any case, I need to redirect requests by an already logged-in user either to `/customer` or to `/admin`, so there's no reason to implement this redirection separately for the case where the user has just logged in. – Ron Inbar Apr 18 '21 at 15:35
  • May be use guard at level app and then decided where to redirect –  Apr 18 '21 at 15:59
  • 1
    @RonInbar I see. Usually I define an emitter which fires a next event whenever the user "state" changes (logged in, logged out). A "InitialPageService" can subscribe to this event and do the redirection. This way it's not bound to a particular route. – c_froehlich Apr 18 '21 at 16:30

2 Answers2

3

You could create some dummy component and protect it with a guard that handles the correct navigation. Something like this:

[
  {
    path: 'loggedin',
    component: DummyComponent,
    canActivate: [HandleLoginGuard]
  },
  {
    path: 'admin',
    component: AdminComponent,
  },
  {
    path: 'customer',
    component: CustomerComponent,
  }
]


@Injectable()
class HandleLoginGuard implements CanActivate {
  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) {
    if(isAdmin()){
        route.navigate(/admin);
    }else{
        route.navigate(/customer);
    }
    return false;
  }
}

This way you can put the logic in a guard instead of the login component.

Robin Dijkhof
  • 18,665
  • 11
  • 65
  • 116
  • 1
    Actually there's no need for a dummy component. You can define a component-less route with an empty `children` array. – Ron Inbar Apr 21 '21 at 11:25
1

What I ended up doing is this:

// app-routing.module.ts

const routes: Routes = [
  {
    path: '',
    canActivate: [AuthorizationGuard],
    children: [],
  },
  { path: '**', component: NotFoundPage },
];
// authorization.guard.ts

@Injectable({
  providedIn: 'root',
})
export class AuthorizationGuard implements CanActivate {

  constructor(private router: Router, private authService: AuthService) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): UrlTree {
    const user = this.authService.getLoggedInUser();
    return (
      (user?.role === 'admin' && this.router.parseUrl('/admin')) ||
      (user?.role === 'customer' && this.router.parseUrl('/customer')) ||
      this.router.parseUrl('/auth')
    );
  }

}
Ron Inbar
  • 2,044
  • 1
  • 16
  • 26
  • So basically, you did as I suggested? – Robin Dijkhof Apr 24 '21 at 19:59
  • @RobinDijkhof Basically, yes, but there are some subtle differences. For one, the `/admin` and `/customer` routes are defined in separate modules. Also, as I pointed out in my earlier comment, I didn't use a dummy component. – Ron Inbar Apr 24 '21 at 21:30
  • I don't see routes for `/admin` and `/customer` defined here so I don't think it technically answers your question. Also - your current guard has more than one responsibility and might be best served as two guards. When you see something like `AdminGuard` in `app-routing.module.ts` you would know exactly what it's doing instead of something like `AuthenticationAndRouteBasedAuthorizationGuard`. Anyway, glad you found a solution that didn't require a dummy component. – Dean Apr 27 '21 at 17:45
  • @Dean Refer to the original question. The `/admin` and `/customer` routes are defined in their respective modules. – Ron Inbar Apr 28 '21 at 10:28