2

Having these routes defined in app.module.ts

...
{ path: 'mypath', component: MyComponent, canActivate: [RouteGuard] }, 
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: '**', redirectTo: '/home'}

and this guard service

import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { AngularFire } from 'angularfire2';
import { AngularFireAuth } from 'angularfire2';

@Injectable()

export class RouteGuard implements CanActivate {

  private loggedIn: boolean;

  constructor ( private af: AngularFire, private auth: AngularFireAuth ) {}

  canActivate(): boolean {

    this.auth.subscribe(auth => {
      if  (auth)  { this.loggedIn = true; }
      else        { this.loggedIn = false; }
//          this.router.navigate(['/login']); for future implememtation
       });

    return this.loggedIn;
  }
}

refreshing the browser from url //mysite/mypath the app goes to //mysite instead of //mysite/mypath (and also skips the default route).

Without any guard activated on mypath route (ie: { path: 'mypath', component: MyComponent }everything works perfectly.

Does anybody knows if this is an Angular bug ? How could I avoid this behaviour ?

My dev env is:

@angular/cli: 1.0.0-rc.1
node: 7.5.0
os: linux x64
@angular/cli: 1.0.0-rc.1
@angular/common: 2.4.9
@angular/compiler: 2.4.9
@angular/compiler-cli: 2.4.9
@angular/core: 2.4.9
@angular/flex-layout: 2.0.0-rc.1
@angular/forms: 2.4.9
@angular/http: 2.4.9
@angular/material: 2.0.0-beta.2
@angular/platform-browser: 2.4.9
@angular/platform-browser-dynamic: 2.4.9
@angular/router: 3.4.9

Thanks for helping me.

PS: I have tried to find an answer both on Stack Overflow or on Github issues without finding an answer. I saw a similar question Angular 2 routing not working on page refresh with Apache, but this is not my case.

Community
  • 1
  • 1
madHorse
  • 109
  • 4
  • 13

4 Answers4

4

you can an asynchronous result to the route guard :

 canActivate(): Promise<boolean> {
   let self = this;
    return this.auth.toPromise().then(auth => {
         if  (auth)  { self.loggedIn = true; }
         else { self.loggedIn = false; }

         self.router.navigate(['/login']); for future implememtation

        return self.loggedIn;
    });


}
El houcine bougarfaoui
  • 35,805
  • 9
  • 37
  • 37
2

Thanks to the suggestion of @Bougarfaoui El houcine and YounesM here is my new guard service (fully taken from this post: Create a Full Angular Authentication System with Firebase

It works perfectly and solve my problem.

import { CanActivate, Router } from '@angular/router';
import { AngularFireAuth } from "angularfire2/angularfire2";
import { Injectable } from "@angular/core";
import { Observable } from "rxjs/Rx";
import 'rxjs/add/operator/do';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/take';

@Injectable()
export class RouteGuard implements CanActivate {

    constructor(private auth: AngularFireAuth, private router: Router) {}

    canActivate(): Observable<boolean> {
      return Observable.from(this.auth)
        .take(1)
        .map(state => !!state)
        .do(authenticated => {
      if 
        (!authenticated) this.router.navigate([ '/login' ]);
      })
    }

}
Community
  • 1
  • 1
madHorse
  • 109
  • 4
  • 13
1

You're making an asynchronous call to your auth service, that means canActivate() returns this.loggedIn before it sets its value to true or false. And since the guard is called every time you take the 'mypath' route this.isLoggedin will be reset to null

Now to avoid this behavior you can use a boolean outside of your guard and use that boolean to keep track of if you're logged in or not.

YounesM
  • 2,279
  • 15
  • 28
0

Thanks to @YounesM's answer I figure out how to solve this.

Basically, all you have to do is to set the returned value to true from the beginning, then since you are only redirecting on false, you can simply return the value at the end of your canActivate() method.

Take a look at this example below and pay attention to the routeActivator:

import { Injectable } from '@angular/core';
import {
  ActivatedRouteSnapshot,
  CanActivate,
  Router,
  RouterStateSnapshot,
} from '@angular/router';
import { Observable } from 'rxjs';
import { AuthService } from '../../auth/auth.service';

@Injectable()
export class PrincipalGuard implements CanActivate {
  profile: any;
  routeActivator: boolean = true;
  constructor(private authService: AuthService, private router: Router) {}

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | Observable<boolean> | Promise<boolean> {
    this.authService.getSignedInUserProfile().subscribe((profile) => {
      this.profile = profile;
      if (this.profile.title != 'Principal') {
        return this.router.navigate(['/dashboard']);
      } else {
        return this.routeActivator;
      }
    });

    return this.routeActivator;
  }
}