Search code examples
angularrxjsobservableangular-guards

Angular router doesn't work when guards return an Observable


I want to redirect users who are not logged in when they try to access protected routes but redirection does not work because the function that checks if the user is logged in returns an Observable.

This is my code in PrivateGuard:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AmplifyService } from 'aws-amplify-angular';

@Injectable({
  providedIn: 'root'
})
export class PrivateGuard implements CanActivate {

  constructor (private _router: Router,
               private _amplifyService: AmplifyService) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this._amplifyService.authStateChange$.pipe(map(authState => {
        console.log(authState);
        if (authState.state === 'signedIn')  {
          return true;
        }else{
          this._router.navigate(['/login']); // Why Never redirect?
          return false;
        }
      })
    );
  }
}

app.routes.ts:

import { RouterModule, Routes } from '@angular/router';
import { LoginComponent } from './components/public/login/login.component';
import { HomeComponent } from './components/private/home/home.component';

const APP_ROUTES: Routes = [
  { path: 'login', component: LoginComponent},
  { path: 'home', component: HomeComponent, canActivate: [PrivateGuard] },
  { path: '**', pathMatch: 'full', redirectTo: 'home' }
];

export const APP_ROUTING = RouterModule.forRoot(APP_ROUTES);

If I write console.log in else statement when I'm logged in, this console.log appears in console, but never redirect to homeComponent

Console when I try navigate to a private route

The route in url dissapears, instead of localhost:4200/login

Thank you for any response.

SOLVED

Finally I use promise instead of observable. The code is the following:

import { Injectable } from '@angular/core';
import { CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
import { AmplifyService } from 'aws-amplify-angular';
import { Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class PrivateGuard implements CanActivate {

  constructor (private _router: Router,
               private _amplifyService: AmplifyService) {}

  canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
    return this._amplifyService.auth().currentAuthenticatedUser()
      .then(() => {
        return true;
      })
      .catch(() => {
        this._router.navigate(['/login']);
        return false;
      });
  }
}

Thank you all for your answers


Solution

  • Your observable is probably never completing, it stills wait for authStateChange$ to emit more values.

    You can achieve what you want by using the take operator (which will unsubscribe after one value emits :

    return this._amplifyService.authStateChange$.pipe(
       take(1), // completes the observable on first emited value
       map(authState => /* */)
    );