7

I'm having issues with the Angular router during the process of navigating to a provided state.

I have tried using a custom guard with canLoad() and canActivate() functions returning booleans as true without any luck.

The Angular docs states the following:

NavigationCancel: An event triggered when navigation is canceled. This is due to a Route Guard returning false during navigation.

As I dont get any more debugging information from the Router tracing (no reason is given) I am forced to check here if there is a fix, or if this is an existing bug. I also appreciate any information regarding other means of debugging the router in Angular 6.

Console output

console output

I've created a small project in here. The project requires access to a provider, I'm using the the provided OpenID Connect provider given in the repository of angular-oauth2-oidc. Password/username is max/geheim.

How to reproduce the error

  1. Clone the repo and serve the site at localhost:4200
  2. Go to localhost:4200/oversikt/index
  3. Login with max/geheim as username/password
  4. Read console

UPDATE I suspect it has something to do with navigating to children: [] routes.

Gjert
  • 1,069
  • 1
  • 18
  • 48
  • You really need to provide 1. The route that is registered. Preferably all of them, in case there is a conflict. 2. The guard so we can see if there's an issue there. 3. The call that's being made. 4. Any additional resolvers or guards being used. – Amos47 Oct 10 '18 at 21:54
  • @Amos47 Relevant code and full code of relevant files have been added – Gjert Oct 10 '18 at 22:14
  • It seems like `DashboardComponent` is not imported into the router module. Either directly or through another `DashbordModule`. Does adding `DashboardComponent` to `declarations` solve your problem? – Amos47 Oct 11 '18 at 15:10
  • Also. Does it work if you remove the guards entirely? I think the guards may be a red herring. – Amos47 Oct 11 '18 at 15:12
  • @Amos47 `DashboardComponent` is declared in the `dashboard.module.ts` which imports the `dashboard-routing.modules.ts`. Also I am able to type in the route directly and get the dashboard component viewed. The problem is the `this.router.navigateByUrl(this.oauthService.state);` canceling for some reason. – Gjert Oct 12 '18 at 09:06
  • @Amos47 I also tested removing the guards completely, still cancels out the navigateByUrl. – Gjert Oct 12 '18 at 09:09
  • Any reason you can't use `router.navigate([this.oauthService.state])`. I've noticed `navigateByUrl` being a little flakey since 6.0. – Amos47 Oct 12 '18 at 16:20
  • @Amos47 Same problem with `router.navigate([])`. – Gjert Oct 13 '18 at 07:46
  • @PhyCoMath could you provide a [mcve] ? Most of the time, creating a MCVE makes you resolve your issue on your own. –  Oct 15 '18 at 08:10
  • @PhyCoMath I'm not sure, but why do you export `RouterModule` from Dashboard and in general what is the idea to export `Angular Core module`? – BotanMan Oct 15 '18 at 20:57
  • Both Code Sandbox or Stackblitz are good places to set a Angular app MCVE fast. – Machado Oct 16 '18 at 17:22
  • my network is not allowing me to check your code on GitHub Gist but can you make sure you are using routerLink instead of href attribute in your tags where you are giving the routing links! Ignore if you already have it! – alokstar Oct 17 '18 at 14:47
  • @trichetriche I got some weird error when adding it to Code Sandbox, however, I created a public repository which can simply be cloned and run directly, [GitHub Repo](https://github.com/ggjersund/angular-router-bug) – Gjert Oct 19 '18 at 21:07
  • Can anyone tell me, what is the issue here? OP provided TDLR story without any statement of what is not working exactly. – Antoniossss Oct 19 '18 at 21:17
  • @Antoniossss The problem is a random `Router Event: NavigationCancel` occuring without providing any reason, and router debugger isn't giving me much to work on. – Gjert Oct 19 '18 at 21:19
  • How to reproduce that having your repo? – Antoniossss Oct 19 '18 at 21:27
  • @Antoniossss Just updated it :) also reduced the example repo code – Gjert Oct 19 '18 at 21:28
  • You see, there is the problem - it works normal for me..... – Antoniossss Oct 19 '18 at 21:47
  • @Antoniossss Could you provide browser info? Or could this be caused by a bug in Webpack? That repo uploaded on GitHub is the exact code running on my computer. So must be some local dev packages. – Gjert Oct 19 '18 at 21:48
  • @PhyCoMath im counting on that bounty ;) – Antoniossss Oct 20 '18 at 04:08

1 Answers1

18

I did a little bit of debugging in your code, and I have found that is not because of links, auth guard, navigation declaration or module configuration nor router beeing flakey in AG6, but it is because of..... OAuth lib you are using.

But let me explain. I found out that following scenario is happening:

  1. You login and beeing redirected back to page
  2. You come back to the application with link like /#YOUR AUT TOKEN
  3. First navigation is scheduled (id 1)
  4. Navigation results in redirection to subpage /oversikt
  5. Navigation 2 pops up (redirection)
  6. Router performs all operations in async way - it is they are serializedbut stilluses RxJS hard- so actual processing is delayed - it has passed current navigation id as argument (that is important)
  7. Some listeners and other subscriptions fires as well as stuff from your OAUTH lib that you are using
  8. It (lib) detects that you have token in your url and REMOVES IT
  9. That triggers another redirection - routing id is INCREMENTED RIGHT AWAY to 3
  10. Now navigation 2 continues and at some point (remember JS is single threaded) it checks (well id does that A LOT) if navigation id from the beginning of the navigation changed or is still the same.
  11. Since it has changes - id 2 was passed at schedule time, and current navigation is 3, single false is propagated all over whole pipes and subscription in router
  12. Having boolean (not even false, but bool) as a value in pipe results in CANCEL navigation without any reason.

Ill add some code references. But in general, that is what happening. Your oauth lib modifies url during navigation and that causes it to be canceled. Guards had like nothing to do with that in straight forward way.

So in general - it is not canceled because "access is denied" like in case of guards, but it is canceled because new navigation will have to be performed, so it is short-circuited by cancel.

Here is (not all) related code:

OAuth lib modifying

   if (!this.oidc) {
            this.eventsSubject.next(new OAuthSuccessEvent('token_received'));
            if (this.clearHashAfterLogin && !options.preventClearHashAfterLogin) {
                location.hash = '';
            }
            return Promise.resolve();
        }

Nav triggering on url change:

 Router.prototype.setUpLocationChangeListener = function () {
        var _this = this;
        // Don't need to use Zone.wrap any more, because zone.js
        // already patch onPopState, so location change callback will
        // run into ngZone
        if (!this.locationSubscription) {
            this.locationSubscription = this.location.subscribe(function (change) {
                var rawUrlTree = _this.parseUrl(change['url']);
                var source = change['type'] === 'popstate' ? 'popstate' : 'hashchange';
                if(this.rou)
                var state = change.state && change.state.navigationId ?
                    { navigationId: change.state.navigationId } :
                    null;
                setTimeout(function () { console.error("FROM LOCATION SUB");_this.scheduleNavigation(rawUrlTree, source, state, { replaceUrl: true }); }, 0);
            });
        }
    };

Nav id modification - happens right away:

   var id = ++this.navigationId;
    console.error("ANOTHER SCHEDULED LOL LOL LOL!!!");
    this.navigations.next({ id: id, source: source, state: state, rawUrl: rawUrl, extras: extras, resolve: resolve, reject: reject, promise: promise });
    // Make sure that the error is propagated even though `processNavigations` catch
    // handler does not rethrow
    return promise.catch(function (e) { return Promise.reject(e); });

That is what is passed to router to start "async" routing - id is nav id (incremented previously)

 Router.prototype.runNavigate = function (url, rawUrl, skipLocationChange, replaceUrl, id, precreatedState) {

This check (it in runNav) fails as first as id changed so 2!==3 - FALSE is returned to the pipe

var preactivationCheckGuards$ = preactivationSetup$.pipe(mergeMap(function (p) {
            if (typeof p === 'boolean' || _this.navigationId !== id) //NAVIGATION ID CHANGES HERE!
            {
              console.warn("PREACTIVATE GUARD CHECK ");
              console.log(p);
              // debugger;
              return of(false);
            }

there are couple of more subscriptions in chaing, all of them have some piped mappings etc as well as known condition check.

 var preactivationResolveData$ = preactivationCheckGuards$.pipe(mergeMap(function (p) {
                if (typeof p === 'boolean' || _this.navigationId !== id)
                    return of(false);

Notice that is what I wrote earlier, that if you get ANY booean here, false is pushed forward. Since we have false here already beacause check failed in previous pipe-map....

Finally at the end of the chain

    if (typeof p === 'boolean' || !p.shouldActivate || id !== _this.navigationId || !p.state) {
      // debugger;
        navigationIsSuccessful = false;
        return;
    }

result flag set to false and this results in

 .then(function () {
        if (navigationIsSuccessful) {
            _this.navigated = true;
            _this.lastSuccessfulId = id;
            _this.events
                .next(new NavigationEnd(id, _this.serializeUrl(url), _this.serializeUrl(_this.currentUrlTree)));
            resolvePromise(true);
        }
        else {
            _this.resetUrlToCurrentUrlTree();
            _this.events
                .next(new NavigationCancel(id, _this.serializeUrl(url), ''));
            resolvePromise(false);
        }

NavigationCancel without any message (last param is the message - emtpy string here) that you know well :).

It took me much more than it should as I didn't know angular internals + those bloody pipes... pipes everywhere.

as for docs

NavigationCancel: An event triggered when navigation is canceled. This is due to a Route Guard returning false during navigation.

well they forgot to mention that internally router can cancel navigation is there is queue of navigaations building up :)

Cheers!

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
  • 1
    Thank you so much, I would never have found this error myself. I suspected something to do with the hash but assumed that the library was handling that correct. – Gjert Oct 20 '18 at 07:25