Skip to content

Why resolves in routes executing after canActivate and canActivateChild? #15026

Closed

Description

@steven-zou

I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[ x ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior
Why resolves in routes executing after canActivate and canActivateChild? Some activate actions are based on the data returned by the calling in resolve. However, currently the canActivate and canActivateChild action will executed firstly before resolves. That is not reasonable, right?

Expected behavior

canActivate and canActivateChild action will executed after resolve returned.

Minimal reproduction of the problem with instructions

What is the motivation / use case for changing the behavior?
Session check etc.

Please tell us about your environment:
npm start

  • Angular version: 2.0.X
    2.0.6

  • Browser: [all | Chrome XX | Firefox XX | IE XX | Safari XX | Mobile Chrome XX | Android X.X Web Browser | iOS XX Safari | iOS XX UIWebView | iOS XX WKWebView ]
    All browsers where this could be reproduced

  • Language: [all]
    all

  • Node (for AoT issues): node --version =
    v6.9.5

Activity

  1. dawidgarus commented on Mar 9, 2017

    @dawidgarus

    I think the idea behind this was:
    Check if user can activate route (ex. has rights to enter) and if so, get (resolve) the data.
    Not the other way around.
    You can always implement "can activate" logic inside resolver, for example:
    Get session data from server and then check if user "can activate" the route, like in the example in docs (https://angular.io/docs/ts/latest/guide/router.html#!#resolve-guard). We resolve the resource, and if it's not present (we cannot activate), we redirect.

  2. DzmitryShylovich commented on Mar 9, 2017

    @DzmitryShylovich
    Contributor

    Usually to make an api call inside a resolver you need an auth token. So to get this token angular runs canActivate first

  3. steven-zou commented on Mar 10, 2017

    @steven-zou
    Author

    @dawidgarus @DzmitryShylovich :

    We did as what you two mentioned at present, implement the auth guard logic in resolve. The reason I post this issue here is the angular document says resolve is used to pre-fetch data from backend and canActiviate/canChildActiviate are used to do some justifications whether next route can be activated or not as their names representing.

    Additionally, the resolve is also not a perfect solution to do auth guard when there are multiple children routes. If the route is switched between children routes, the resolve you put on their parent route will not be executed. If you want to make sure each route switching can trigger the resolve logic , you need to put it to every route needed. Not very convenient.

    const myRoutes: Routes = [
      {
       path: "parent", 
       component: ParentComponent,
       resolve: {
           myResolve: MyResolve
       },
       children: [
          {
             path: "child1",
             component: ChildComponent1
             /*,resolve: {
           myResolve: MyResolve
       }*/
          },
          {
             path: "child2",
             component: ChildComponent2.
             /*,resolve: {
           myResolve: MyResolve
       }*/
          }
       ]
      }
    ];
    
  4. Toxicable commented on Mar 10, 2017

    @Toxicable

    @steven-zou Your auth logic should be in canActivate but for something that you need frequent access to, ie and access_token the usual implementation is to retrieve it on app boot or on login, then save it somewhere either in memory or in localStorage (or similar) so when you need to use it it's readily available without using resolve making this ordering or resolve/canActivate a non issue.
    If you want to discuss this further ask on https://gitter.im/angular/angular or Stack overflow since this is not the appropriate place for support requests

  5. bfricka commented on Mar 12, 2017

    @bfricka

    I disagree. One of the few things the router does well is determine whether a route can be activated before trying to resolve the data required for that route. Resolves are for the entry component of the route you're activating. If you need to resolve data to determine whether the route can be activated, the CanActivate interface allows you to return a promise or observable just as the Resolve interface does, so I don't see how this is an issue.

  6. steven-zou commented on Mar 13, 2017

    @steven-zou
    Author

    @bfricka

    Per my understanding, the current execution order of resolve and canActivate is 'work as design', right? Checked the api references, the method 'canActivate' in activate class does support return Promise or Observable with boolean. I though it only supported returning single boolean value.

    Additionally, configure the resolve in the parent route, should it be triggered or should NOT when switch the route between child routes? Currently, I found switching routes between child routes will not trigger the resolve configured in parent route, is it a issue or just 'work as design'? (I gave the example routing configurations in above comment)

  7. bfricka commented on Mar 13, 2017

    @bfricka

    @steven-zou you are incorrect about the CanActivate interface. We use Promise<boolean> and Observable<boolean> in our app currently.

    As for resolving parent routes, you are unfortunately correct, and this is one of many things that is poorly designed in the router. Thankfully, there's evidence that ng4 router will fix this. Also, ui-router looks like it's gotten to a place where it may be a good replacement, so I'd recommend looking into it. Last time I used it they were importing all of RxJS, but they have since fixed this.

  8. steven-zou commented on Mar 17, 2017

    @steven-zou
    Author

    Ok, things clear. I'll close this issue. Thanks.

  9. cguilhermef commented on Jun 22, 2017

    @cguilhermef

    Hmmm, and in another cenario, where I need some data before to activate that route, how can I NOT activate it if resolve fail?

  10. xaralis commented on Dec 5, 2017

    @xaralis

    This is totally flawed in case of children routes any time you develop something a little more complicated than just a todo list. Usually, you would expect guards/resolves in parent routes are already resolved, so you can access the data in children guards/resolves. Unfortunately, this sequence of (logical) behavior is not put in place with current Angular router. Instead, it is followed only with guards and not for resolvers.

    This becomes a real pain in the ass if your child guards require something which should have been dealt with one level above in the route hierarchy like getting some record that has to be resolved (and available) for all the child routes.

    Am I something missing?

  11. code-penguin commented on Dec 14, 2017

    @code-penguin

    I have the same issue as xaralis. Do resolvers only run after guards on all levels of the requested route have been checked? I have a top-level resolver that loads some essential data, and then need to use this data in a guard on a child route. But the guard runs before the resolver, so it errors because the data is not there yet. My expectation was that guards would run before resolvers only on a single level, so you would have maximum flexibility.

  12. xaralis commented on Dec 14, 2017

    @xaralis

    @code-penguin It's exactly as you describe it. It's always guards first (from top to bottom) and resolvers after that. From my point of view, this is huge constraint that basically makes resolvers unusable. Instead, we do all the heavy lifting in guards :-/

  13. miloszsobczak commented on Jan 4, 2018

    @miloszsobczak

    I do agree with @xaralis - imho routing should order parent/children concept while no matter it's resolver nor guard. Simple SSO is totally messed up in my case.

  14. xaralis commented on Jan 4, 2018

    @xaralis

    @miloszsobczak Yep, exactly. Currently, resolvers are totally useless. In our case, we're doing all the logic in guards (ensure the data is loaded and stored inside ngrx store) and let the resolvers only read the data from the store and expose it for simpler access in the components. But if feels weird and we're probably trash the resolvers altogether eventually.

  15. patricknazar commented on Jan 4, 2018

    @patricknazar

    @xaralis came to the same conclusion just now. Also am using ngrx store for this. canActivate() returns a store property selector observable with a filter operator to determine if data is not false. In my case loading the data happens as a dispatched action in AppComponent ngOnInit(), which is picked up by an ngrx effect which calls a service. When the data isn't false (after the filter, crucial), the guard see's that and loads the route, which has components using the same selectors.

  16. miloszsobczak commented on Apr 10, 2018

    @miloszsobczak

    why it's closed?

  17. mlc-mlapis commented on Apr 10, 2018

    @mlc-mlapis
    Contributor

    @miloszsobczak ... because the author of the issue closed it?

    ... btw: There is also still the same reason why canActivate and canActivateChild should run before resolvers.

  18. SamuelMS commented on May 17, 2018

    @SamuelMS

    This generally defeats the purpose of route guards for async dependencies.

    Imagine this use-case:

    // loaded in the root app router
    const appRoutes: Routes = [
        {
            path: '',
            resolve: {
                config: ConfigResolver,
                user: UserResolver,
            },
            children: [
                { path: 'error', component: ErrorComponent },
                {
                    path: 'somewhere',
                    loadChildren: 'app/somewhere/somewhere.module#SomewhereModule'
                },
                {
                    path: 'elsewhere',
                    loadChildren: 'app/elsewhere/elsewhere.module#ElsewhereModule'
                },
                { path: '**', redirectTo: 'somewhere' }
            ]
        },
        { path: '**', redirectTo: '' }
    ];
    
    // loaded in a fictional "somewhere" router
    const somewhereRoutes: Routes = [
        {
            path: '',
            resolve: {
                userWorkspace: UserWorkspaceResolver,
            },
            children: [
                {
                    path: '',
                    component: WorkspaceDetailComponent,
                    canActivateChild: [ WorkspaceDetailGuard ],
                    children: [
                        // ...
                    ],
                },
                {
                    path: 'onboarding',
                    component: WorkspaceOnboardingComponent,
                    canActivateChild: [ WorkspaceOnboardingGuard ],
                    children: [
                        // ...
                    ],
                },
                { path: '**', redirectTo: '' }
            ]
        },
        { path: '**', redirectTo: '' }
    ];

    Pretty simple. Two relevant modules: the root "app" module and a lazy-loaded "somewhere" module. Pardon the naming.

    A bunch of top-level information is resolved at the highest-order routes, and then more information about some fictional "workspace" is resolved within the somewhere module.

    Here's the catch: the "WorkspaceDetailGuard" and "WorkspaceOnboardingGuard" guards are dependent on the workspace resolved at a parent route (via UserWorkspaceResolver), which in turn is dependent on the user data loaded even before then (via UserResolver).

    Per the explanations in this thread, this will never work as-is, since guards (CanActivate, CanActivateChild, etc) are always processed before resolvers can even begin their work. This makes sense if you ask "why start loading data before we know the user can even activate XYZ route" -- but only from a strictly synchronous standpoint; there's no reason to assume users will have all the information they need to properly "guard" routes before they make any asynchronous calls.

    So, the apparent solution from this issue is to convert all async guards to resolvers, and do work in them -- or convert all resolvers to guards -- so that they fire in hierarchical order and can properly depend on each other. But this is not intuitive at all from a segregation of duties standpoint: guards are meant to handle authorization, and resolvers load data. In either of these scenarios, you're essentially using one or the other, but not both; you'll have guards that load data, or you'll have resolvers that handle authorization. This is backwards and is confusing.

    Let me know if I'm misunderstanding something here; otherwise, I'd like to re-open this ticket and suggest that Angular handle guards first, then resolvers, but follow the parent -> child route hierarchy; in other words, guarantee that parent route data is resolved before child route logic fires.

  19. code-penguin commented on May 18, 2018

    @code-penguin

    @SamuelMS yes, you are correct, great summary of the issue. I also hope that the ticket will be re-opened..

  20. xaralis commented on May 18, 2018

    @xaralis

    Also see this: #20805 (comment)

  21. SamuelMS commented on May 30, 2018

    @SamuelMS

    Didn't realize the original author of this issue closed it; moving this to #24187 to track discussion of the guard/resolver paradigm.

  22. angular-automatic-lock-bot commented on Sep 13, 2019

    @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.

  23. locked and limited conversation to collaborators on Sep 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    area: routerfeatureLabel used to distinguish feature request from other issues

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions