Angular 5 navigation cancelled after guard returns

2019-07-23 06:19发布

问题:

I have three routes, one of them protected with a guard

app.routing-module.ts

import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { LoginComponent } from './modules/auth/login/login.component';
import { DashboardComponent } from './modules/dashboard/dashboard.component';
import { CustomersComponent } from './modules/customers/customers.component';

import { AuthGuard } from './shared/guards/auth.guard';


const routes: Routes = [
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full'
  },
  { path: 'home', component: DashboardComponent, canActivate: [AuthGuard] },
  { path: 'login', component: LoginComponent },
  { path: 'customers', component: CustomersComponent },
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule],
  providers: [AuthGuard]
})
export class AppRoutingModule { }

The guard gets if user is auth from ngrx store and returns an Observable to allow or not to activate the route

import { Injectable } from '@angular/core';
import {
  CanActivate,
  ActivatedRouteSnapshot,
  RouterStateSnapshot,
  Router
} from '@angular/router';
import { Observable } from 'rxjs/Observable';
import { catchError, map, switchMap, filter, take } from 'rxjs/operators';
import { of } from 'rxjs/observable/of';
import { Store } from '@ngrx/store';

import { getUserAuth } from '../../store/reducers';


@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private store: Store<any>, private router: Router) { }

  getUserAuth(): Observable<any> {
    return this.store.select(getUserAuth).pipe(
      filter((data: any) => data),
      take(1)
    );
  }

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<boolean> | Promise<boolean> | boolean {
    return this.getUserAuth().pipe(
      switchMap(() => of(true)),
      catchError(() => of(false))
    );
  }
}

When switching between the two "public" routes the navigation works fine, but once trying to access to the protected route and gets cancelled (user is not auth), the navigation is cancelled and none of the routes can be accesed. On the other hand if the user is logged, the navigation works fine and the three routes are accesible.

If I return a simple Observable.of(false) in the canActivate method, the protected route can not be accesed but the navigation to the other routes still works

canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
): Observable<boolean> | Promise<boolean> | boolean {
    return Observable.of(false);
}

Why is the navigation cancelled?

EDIT

I forgot to say that I'm using @ngrx/router-store so my app.module looks like:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    CoreModule,
    StoreModule.forRoot(rootReducers, {metaReducers}),
    StoreRouterConnectingModule.forRoot({
      stateKey: 'router' // name of reducer key
    }),
    StoreDevtoolsModule.instrument({ maxAge: 25 }),
    EffectsModule.forRoot(rootEffects),
    AuthModule,
    DashboardModule,
    CustomersModule
  ],
  providers: [{ provide: RouterStateSerializer, useClass: CustomSerializer }],
  bootstrap: [AppComponent]
})

回答1:

The problem lies here:

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private store: Store<any>, private router: Router) { }

  getUserAuth(): Observable<any> {
    return this.store.select(getUserAuth).pipe(
      filter((data: any) => data),
      ^^^^^^
      take(1)
    );
  }

The AuthGuard will pause navigation until the Observable resolves to either true or false. Since you're filtering any falsy null values, the Observable will not emit anything when the user is not authenticated. You should map the values to true / false instead:

return this.store.select(getUserAuth).pipe(
  map((data: any) => !!data),
  ^^^^^^^^^^^^^^^^^^^^^^^^^
  take(1)
);