angular: canLoad guard does not subscribe to Observable

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

About this issue

  • Original URL
  • State: closed
  • Created 7 years ago
  • Reactions: 7
  • Comments: 29 (11 by maintainers)

Commits related to this issue

Most upvoted comments

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);
  }

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.

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

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 😃

Alright, that was something I overlooked then.

My point still stands though - Code should work for all guards the same. canLoad should act the same in terms of observables like canActivate

Let’s refocus that issue.

I’ve rework the plunker of @Toxicable to suits original post. http://plnkr.co/edit/Vr05o12PRrVmpGXwjkwq?p=preview

So while both guard has the same implementation only one works. This may be a docs issue, but I agree it’s disturbing that all guard doesn’t have the same requirement. Also I couldn’t find anything on the docs saying observable returns by canLoad must complete.

@bunnyvishal6 Your have a problem with rxjs (how to import operators) and not with any of Angular or guards. I’ll be pleased to help you on gitter or stackoverflow if you want, but here isn’t the place for that.

There has been discussion before at https://github.com/angular/angular/issues/9613, even it was not be regarded as an Angular bug, a PR https://github.com/angular/angular/pull/10412 still be merged for that.

I agree that they should be consistent anyway.

I don’t think the type of module matters, it’s just about guard types.

I think I found the problem and that this may be an actual bug on canLoad. I’ll open a PR soon about it.

@Z3roCoder you’ve made the same mistake as @markusfalk . the Observable returned from new BehaviorSubject(true) does not complete after it’s first emission, therefore the canLoad does not complete. You need to return an Observable that does complete, using an operator such as take can help you with that. For example return this.obj.take(1);