0

I have an order section in my app:

RouterModule.forChild([
  {
    path: '',
    component: OrderComponent,
    canActivate: [OrderGuard],
    children: [
      {
        path: '',
        component: OrderInfoComponent
      },
      {
        path: 'approved',
        component: OrderApprovedComponent
      },
      {
        path: 'declined',
        component: OrderDeclinedComponent
      }
    ]
  }
])

I have to redirect the user to the appropriate page depending on the order state. I created a guard:

canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): UrlTree {
  const order = ...;   // Imagine we fetch the order here...
  let path: string;
  switch (order.state) {
    case OrderState.Default:
        path = '';
        break;
    case OrderState.Approved:
        path = 'approved';
        break;
    case OrderState.Declined:
        path = 'declined';
        break;
  }
  return this.router.parseUrl(`${state.url}/${path}`);
}

That's what's happening (for order.state === 'approved'):

  1. The user goes to /order.
  2. Guard redirects the user to /order/approved.
  3. The user is on /order/approved.
  4. Guard redirects the user to /order/approved/approved.
  5. The error Cannot match any routes is being thrown.

In other words, it goes into infinite loop. How can I break it? I was looking at this solution, but it leaves the skip query parameter after the redirection and it does not work with UrlTree since we cannot pass any query parameters. I also want to redirect the user properly if they went to a child route (/declined or /approved).

Boris
  • 4,944
  • 7
  • 36
  • 69
  • It sounds like your guard is more appropriate at the child level. I haven't ever seen `RouterModule.forChild` before though. Is this lazy loading? – mwilson Apr 29 '20 at 15:54
  • Wasn’t there a canActiveChild as well..? – MikeOne Apr 29 '20 at 16:00
  • @mwilson, yes, it is a lazy module. I want it to work on both root and children, because it will be /order link in the menu. – Boris Apr 29 '20 at 16:18
  • @MikeOne, I tried canActivateChild as well. – Boris Apr 29 '20 at 16:18
  • IMO I think this is more of a design issue. Guards are mainly for preventing users from accessing ("activating") routes. Not really designed for general purpose redirects. Yes, you can use them that way, but this doesn't sound like the right place for it. You're basically trying to make a guard work as an workflow routing system. Why not just bake that logic into a component/directive/navigation-resolver to handle your redirect logic? I would make a new route `/order/:status` and that way it can accept a param of 'approved' or 'declined' and grab the data needed. – mwilson Apr 29 '20 at 16:20
  • ...Then, I would handle all the routing in the `/order` component. If you need any logic for protecting those routes, that's the time to implement the guards.. Or, you could just bake in another layer of logic in your guard to just return `true` if the current route is `order/approved` or `order/declined` – mwilson Apr 29 '20 at 16:23
  • @mwilson, the thing is that the UrlTree was added to the Angular in order to implement this kind of logic. I don't want to move the redirects to `/order/:status` because of bloated HTML. Even if this route does not contain HTML but just redirect logic, I'll have to violate DRY by copy/pasting the redirection logic. It's really weird there is no solution on the web I can find just like nobody has encountered that. – Boris Apr 29 '20 at 19:19
  • I think if you look through the angular docs, you are essentially wanting a master/detail setup. Having a parameterized route is how you're supposed to accomplish that. I really don't think guards are designed to fit your use case. If you did move to the parameterized route setup, you can prevent violating DRY by just making it a single component. That components display would change whether the param was 'approve' or 'decline'. Pretty standard stuff. – mwilson Apr 29 '20 at 19:37
  • @mwilson, yes, I could, but I'll have got a bloated component's template since every state has a completely different logic. Moreover, this approach violates the concept of the routing itself IMO. It's a shame Angular doesn't have some kind of middlewares for the logic described above :( – Boris Apr 30 '20 at 08:30
  • If you're scared of "bloated components" then use separate components and leverage component factory to dynamically insert components. The approach I have proposed does not violate routing whatsoever. It is a prime example of a master/detail route. I would suggest going through the Angular Tour of Heroes to familiarize yourself better with how routing is intended to work. It might also help to walk through a couple of guard tutorials to understand how those are intended to be used as well. – mwilson Apr 30 '20 at 14:24

0 Answers0