Solvedangular canLoad guard does not subscribe to Observable<boolean>

I'm submitting a...

[ ] Regression (a behavior that used to work and stopped working in a new release)
[x] Bug report
[ ] Feature request
[ ] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

When passing an Observable to the canLoad guard for a lazyly loaded module routerLink stops navigating to any route even if the boolean in Observable<boolean> is true. No error messages in console.

app-routing.module:

const routes: Routes = [
  {
    path: '',
    component: SomeAComponent,
    pathMatch: 'full'
  },
  {
    path: 'someb',
    component: SomeBComponent,
    canActivate: [
      AuthGuard
    ],
  },
  {
    path: 'lazy',
    loadChildren: './lazy/lazy.module#LazyModule',
    canLoad: [
      AuthGuard
    ]
  },
  {
    path: '**',
    redirectTo: '/'
  }
];

auth.guard:

@Injectable()
export class AuthGuard implements CanLoad, CanActivate {

  /* 
   * @angular-redux/store
   * false --> next() --> true --> ...
   */
  @select() readonly authenticated$: Observable<boolean>;

  canLoad(): Observable<boolean> | boolean {
    // return true; // WORKS
    return this.authenticated$; // ERROR: all routing stops from and to the current page
  }

  canActivate(): Observable<boolean> | boolean {
    // return true; // WORKS
    return this.authenticated$; // WORKS
  }

}

lazy-routing.module:

//...
const routes: Routes = [
  {
    path: '',
    component: LazyComponent
  }
];
//...

Expected behavior

Like the canActivate guard the canLoadguard should subscribe to the Observable.

Minimal reproduction of the problem with instructions

  • set value of the authenticated$ Observable to true
  • lazy load a module and protect it via canLoad guard
  • navigate to the lazy and protected module via <a routerLink="/lazy">Lazy</a>
    => all navigation stops.

Environment

Angular version: 4.3.6


Browser:
- [x] Chrome (desktop) version 60.0.3112.113
- [ ] Chrome (Android) version XX
- [ ] Chrome (iOS) version XX
- [x] Firefox version 55.0.3
- [x] Safari (desktop) version 10.1.2 (12603.3.8)
- [ ] Safari (iOS) version XX
- [ ] IE version XX
- [ ] Edge version XX
 
For Tooling issues:
- Node version: v8.4.0
- Platform:  MacOS 10.12.6

Others:
* @angular-redux/router@6.3.1
* @angular-redux/store@6.5.7
* redux@3.7.2
* rxjs@5.4.3
* typescript@2.3.4
30 Answers

✔️Accepted Answer

Hello, we reviewed this issue and determined that it doesn't fall into the bug report or feature request category. This issue tracker is not suitable for support requests, please repost your issue on StackOverflow using tag angular.

If you are wondering why we don't resolve support issues via the issue tracker, please check out this explanation.


Angular does not complete your Observable for you, you must pass one that does.
For example:

var isLogggedIn = // some observable that never completes

  canLoad(): Observable<boolean> | boolean {
    return this.isLoggedIn.take(1);
  }

Other Answers:

Observable used for guard must be completed.

@trotyl When I saw your first comment stating that guard's observables must complete, I searched the docs to find something and I couldn't find anything. Now that you link it, I think a sentence bury in the middle of the guide refactoring some demo code to implements a resolver isn't what I would call well-documented but rather mentioned somewhere. And I understand why I couldn't find that statement.

So if guard's observables must complete I think we could do better on docs to stipulate it.
But if that is the case I really don't get why canActivate, canDeactivate and canActiveChild all use first operator on the observables. The purpose of first really seems to be to handle multi-value and possibly infinite Observables, I really can't find any other reason.

You're right about the breaking changes, this will not pass the guard at the moment and it will with my PR. But the reason you mentioned is wrong :

The current behavior is using the very last value, namely false.

This is wrong, for canLoad guard, the current behavior is to check all values so both Observalbe.of(true, false) and Observable.of(false, true) will fail to pass the guard. But again this is inconsistent because while your example will fail a canLoad guard it will pass a canActivate guard.

I forgot to check resolve so I thought only canLoad acted differently.
So there is a real inconsistency about guards behavior :

  • canActivate, canDeactive and canActivateChild will only check the first value emitted and won't wait for observables to complete.
  • canLoad will check all the values returned and wait until all observables complete.
  • resolve will only keep the last value emitted by each observable and wait until all observable complete.

From that I think guards are mostly expecting single value observables that complete right after their single emit.
That's why I think Angular should shape all guard's observable into single observable that complete right after (using first) and not users.

Thanks for looking into it!
Will create a question on SO.

@Toxicable: you don't think the different behavior of the two guards could be considered a bug? Maybe it just needs more documentation :)

I do also consider this a bug. canActivate is able to get the value out of the Observable, but canLoad isn't. I haven't seen anything about that in the documentation and it would be expected that the behaviour of both would be the same.

It can be reproduced in this testproject: https://gitlab.com/scaks/myTestApp

More Issues: