1
votes

I'm trying to implement a guard in my project, that should wait until ngrx-store's loading complete and after this check is user authenticated or not. The problem, that guard doesn't wait completion of store's loading.

My app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import {
  IsAuthGuard,
  HomeRedirectGuard
} from '@app/core';
import { LoginComponent } from './static';

const routes: Routes = [
  {
    path: '',
    redirectTo: 'home',
    pathMatch: 'full',
  },
  {
    path: 'settings',
    loadChildren: 'app/features/settings/settings.module#SettingsModule',
    canActivate: [IsAuthGuard, HomeRedirectGuard]
  },
  {
    path: 'login',
    component: LoginComponent,
    data: { title: 'tts.menu.login' }
  },
 
  {
    path: 'home',
    loadChildren: 'app/features/home/home.module#HomeModule',
    data: { title: 'tts.menu.home' },
    canActivate: [IsAuthGuard, HomeRedirectGuard]
  }
  {
    path: '**',
    redirectTo: 'home'
  }
];

@NgModule({
  // useHash supports github.io demo page, remove in your app
  imports: [
    RouterModule.forRoot(routes, {
      useHash: true,
      scrollPositionRestoration: 'enabled'
    })
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }

The idea of IsAuthGuard is just check if we have authentication or not, and dispatch login request action if not authenticated.

IsAuthGuard:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot, UrlSegment, CanLoad, Route } from '@angular/router';
import { Observable, of } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { selectAuthisAuthenticated } from '@app/root-store/auth-store/selectors';
import { AuthStoreActions } from '@app/root-store';
import { take, switchMap } from 'rxjs/operators';

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

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {
    return this.canLoad(route.routeConfig, null);
  }

  constructor(private store: Store<any>, private router: Router) { }

  canLoad(route: Route, segments: UrlSegment[]): Observable<boolean> | boolean {
    console.log("CanLoad isAuth");

    return this.store
      .pipe(select(selectAuthisAuthenticated),
        take(1),
        switchMap(isAuth => {
          if (!isAuth) {
            this.store.dispatch(new AuthStoreActions.AutoLoginRequestAction());
          }
          return of(true);
        }));
  }
}

And finally my HomeRedirectGuard service, that doesn't work:

import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot } from '@angular/router';
import { Observable, Subject } from 'rxjs';
import { Store, select } from '@ngrx/store';
import { selectAuthUser, selectAuthIsLoading} from '@app/root-store/auth-store/selectors';
import { Constants } from '../constants/constants.enum';
import { takeUntil, last, map } from 'rxjs/operators';

@Injectable()
export class HomeRedirectGuard implements CanActivate{
  isUser = false;
  private unsubscribe$: Subject<void> = new Subject<void>();

  constructor(private store: Store<any>, private router: Router) {
    this.store
      .pipe(select(selectAuthUser),
        takeUntil(this.unsubscribe$))
      .subscribe(user => {
        this.isUser = !!user;
        console.log("USER =", user);
      });
  }

  canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): boolean | Observable<boolean> {    
    return this.checkAuth();
  }


  checkAuth(): Observable<boolean> | boolean {
    console.log("CanLoad Home");

     return  this.store.pipe(select(selectAuthIsLoading),
      last(val => val === false),
      map(isLoading => {
        console.log("isLoading = ", isLoading);
        console.log("isUser = ", this.isUser);
        let redirectUrl;

        if (this.isUser) {
          redirectUrl = Constants.HOME_URL;
        } else {
          redirectUrl = Constants.LOGIN_URL;
        }
        this.router.navigate([redirectUrl]);
        console.log("HOME redirectUrl=", redirectUrl);
        return true;
      })
    );
  }
}

And I see in console this: enter image description here

CanLoad isAuth

[DEBUG] action: [Auth] Auto login request {payload: undefined, oldState: {…}, newState: auth: {isAuthenticated: false, user: null, redirectUrl: null, error: null, isLoading: true} ...

USER = null

CanLoad Home

[DEBUG] action: [Auth] Auto login failure {payload: undefined, oldState: {…}, newState: auth: {isAuthenticated: false, user: null, redirectUrl: null, error: null, isLoading: false}

So, I come to HomeRedirectGuard, but Guard doesn't wait last emission of select, where isLoading is false and there are information in store, are there authentication or not.

1
@MauroDias thank you, I have been fixed it with filter((authState) => !authState.isLoading) tooAnnaKoval

1 Answers

3
votes

You can add a skipWhile:

this.store.pipe(select(selectAuthIsLoading),
skipWhile(loading => !loading),
...