Skip to content

Async guards should have the option to wait for previous guards to finish #40577

Closed

Description

@MickL

This is a copy of #21702 which has been locked due to inactivity. Still it is relevant.

I'm submitting a...

[ ] Regression
[ ] Bug report
[X] Feature request
[ ] Documentation issue or request
[ ] Support request

Current behavior

When having multiple async guards (returning promise or observable) for the same path they execute at the same time:

{
  path: '',
  canActivate: [
        AuthGuard,
        DetectLanguage,
        GetAppData,
  ],
  children: [ ... ]
}

Expected behavior

There should be an option to let them execute synchronous:

  • guard[i+1] should wait for guard[i] to finish
  • If guard[i] returns false all following guards should not execute

What is the motivation / use case for changing the behavior?

The example above has an authentication which needs to be done first before getting the app data. AuthGuard returns a token that needs to be used within GetAppData. Currently GetAppData runs immediatly without having a token.

Workaround

Kinda ugly and confusing workaround:

{
      path: '',
      canActivate: [
         AuthGuard,
      ],
      children: [
        {
          path: '',
          canActivate: [
             DetectLanguage,
          ],
          children: [
             {
                path: '',
                canActivate: [
                    GetAppdata,
                ],
                component: MyComponent,
             }
         }
      ]
    }

Activity

  1. added this to the needsTriage milestone on Jan 26, 2021
  2. added
    featureLabel used to distinguish feature request from other issues
    on Jan 26, 2021
  3. modified the milestones: needsTriage, Backlog on Jan 26, 2021
  4. atscott commented on Jan 26, 2021

    @atscott
    Contributor

    Totally reasonable feature request. There are several requests for providing an option to modify how the router handles guards, resolvers, activation, etc. in terms of ordering and such. I don't think it makes sense to add all of these different options as things that can be turned on/off in the ExtraOptions like we do for certain features now. Instead, I hope the router can be reworked in the future to be more composable where it provides the basic building blocks (creating a target url, determining the target route, collecting guards, etc) and passes these along to the set of operations that developers can swap in and out. This would require quite a bit more design and thinking about what the Router should look like long term.

  5. sod commented on Jan 27, 2021

    @sod
    Contributor

    You can use the SequentialRoutingGuardService from https://ng-run.com/edit/HmMVqtmmHB52knus3yjk?open=app%2Fsequential-routing-guard.service.ts from #36538 to achieve this.

    Just change your guards from

    export class MyGuard implements CanActivate {
      canActivate(state) {
        return yourConditionObservable$;
    }

    to

    export class MyGuard implements CanActivate {
      constructor(private sequentialRoutingGuardService: SequentialRoutingGuardService) { }
    
      canActivate(state) {
        return this.sequentialRoutingGuardService.queue(state, yourConditionObservable$);
      }
    }

    and they will run in sequence + cancel remaining guards if one returns false.

  6. angular-robot commented on Jun 4, 2021

    @angular-robot
    Contributor

    Just a heads up that we kicked off a community voting process for your feature request. There are 20 days until the voting process ends.

    Find more details about Angular's feature request process in our documentation.

  7. angular-robot commented on Jun 25, 2021

    @angular-robot
    Contributor

    Thank you for submitting your feature request! Looks like during the polling process it didn't collect a sufficient number of votes to move to the next stage.

    We want to keep Angular rich and ergonomic and at the same time be mindful about its scope and learning journey. If you think your request could live outside Angular's scope, we'd encourage you to collaborate with the community on publishing it as an open source package.

    You can find more details about the feature request process in our documentation.

  8. atscott commented on Jul 23, 2021

    @atscott
    Contributor

    Closing in favor of #42953 (which links to this for tracking and discoverability). This is a good feature request and the ability to configure this type of thing without a bundle size cost to every Router user even if they don't use it would be one of the main goals there.

  9. angular-automatic-lock-bot commented on Aug 23, 2021

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  10. locked and limited conversation to collaborators on Aug 23, 2021
  11. atscott commented on Jul 6, 2022

    @atscott
    Contributor

    This will be somewhat trivial in the user-space if/when the "guards as functions" change is made.

    function runSerially(guards: CanActivateFn[]|CanActivateChildFn[]): CanActivateFn|
        CanActivateChildFn {
          return (route: ActivatedRouteSnapshot, state: RouterStateSnapshot) => {
            const injector = inject(EnvironmentInjector);
            const observables = guards.map(guard => {
              const guardResult = injector.runInContext(() => guard(route, state));
              return wrapIntoObservable(guardResult).pipe(first());
            })
            return concat(...observables).pipe(takeWhile(v => v === true), last());
          }
    }
    

    And your route could be

    {
      path: '**',
      component: SomeCmp,
      canActivate: [runSerially([guard1, guard2, guard3, guard4])]
    }
    
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions