3

In angular2 rc2 with the router module v 3.0.0.6alpha, I extend the RouterOulet to check if the user is logged in before accessing the admin. So this is the code :

@Directive({
    selector: 'router-outlet'
})

export class LoggedInRouterOutlet extends RouterOutlet 
{
    publicRoutes: Array<string>;
    private parentOutletMap: RouterOutletMap;
    private userService: UserService;
    private parentRouter: Router;

    constructor(
        parentOutletMap: RouterOutletMap,
        _location: ViewContainerRef,
        @Attribute('name') name: string,
        userService: UserService,
        parentRouter: Router
    ) { 
        super(parentOutletMap, _location, name);

        this.parentRouter = parentRouter;
        this.parentOutletMap = parentOutletMap;
        this.userService = userService;
        this.publicRoutes = [
            'public', 
            'login'
        ];
    }

    activate(factory: ComponentFactory<any>, activatedRoute: ActivatedRoute, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap) 
    {
        if (this._canActivate(factory.selector)) { 
            return super.activate(factory, activatedRoute, providers, outletMap); 
        }

        this.parentRouter.navigate(['/login']);
    }

    _canActivate(url) {
        return this.publicRoutes.indexOf(url) !== -1 || this.userService.isLoggedIn()
    }
}

userService.isLoggedIn() has to return a boolean. My question is : How do I adapt my code to make an http call to check if the user is logged in ? Because if the isLoggedIn method return an observable object, and I subscribe it, I can't return the result in the parent function.

Adam S
  • 1,021
  • 1
  • 14
  • 25

1 Answers1

8

Please notice that the result of activate method of OutletRouter has changed.

@angular/router-deprecated

activate(nextInstruction: ComponentInstruction) : Promise<any>

@angular/router

activate(factory: ComponentFactory<any>, providers: ResolvedReflectiveProvider[], outletMap: RouterOutletMap) : ComponentRef<any>

which is not a Promise or Observable any more. New router implementation comes with a bit different solution that I think is cleaner: Guards.

A guard's return value controls the router's behavior:

if it returns true, the navigation process continues if it returns false, the navigation process stops and the user stays put The guard can also tell the router to navigate elsewhere, effectively canceling the current navigation.

The guard might return its boolean answer synchronously. But in many cases, the guard can't produce an answer synchronously. The guard could ask the user a question, save changes to the server, or fetch fresh data. These are all asynchronous operations.

Accordingly, a routing guard can return an Observable and the router will wait for the observable to resolve to true or `false.

You can create auth.guard.ts:

import { Injectable }             from '@angular/core';
import { CanActivate,
         Router,
         ActivatedRouteSnapshot,
         RouterStateSnapshot }    from '@angular/router';
import { UserService }            from './user.service';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private userService: UserService, private router: Router) {}

  canActivate(
    // Not using but worth knowing about
    next:  ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ) {
    return this.userService.isLoggedIn();
  }
}

Now make sure your isLoggedIn return Observable (or Promise - try both as Angular2 Reference is not ready yet). In my case API returns JSON in format: { success: true / false }.

public isLoggedIn() : Observable<boolean> | boolean {
    let router: Router = this.router;
    let obs;

    try {
        obs = this.authHttp.get('/api/check/logged')
            .map(result => result.json())
            .map(resultJson => (resultJson && resultJson.success));

    } catch (err) {
        obs = Observable.of(false);
    }

    return obs
        .map(success => {
             // navigate to login page
             if (!success)
                 router.navigate(['/auth/login']);

             return success;
        });
}

Then just modify your RouterConfig array:

{ path: '/secret', component: SercetComponent, canActivate: [AuthGuard] }

Refer also to Angular2 Developer Guide

Baumi
  • 1,745
  • 1
  • 17
  • 31
  • Seems good, thanks ! But I wonder where to inject that guard ? If you can edit your code to show it... – Adam S Jun 17 '16 at 19:50
  • I did few minutes after posting original answer ;) I forgot to add this before. – Baumi Jun 17 '16 at 19:51
  • Neat ! Thanks again – Adam S Jun 17 '16 at 19:52
  • I implemented the code and get "No provider for AuthGuard", yet I added it to my main component provider. Do you know why ? – Adam S Jun 17 '16 at 20:09
  • Try to add to your route.ts: export const AUTH_PROVIDERS = [AuthGuard, UserService]; and then add it in your bootstrap global providers. – Baumi Jun 17 '16 at 20:17
  • I'm using rc2 and I don't see CanActivate from @angular/router. Isn't CanActivate from router-deprecated? – Sam Jun 17 '16 at 21:34
  • 1
    Nope. You probably don't have right router package (it's not in 2.0.0-rc.2). Add to your dependencies in package.json: "@angular/router": "^3.0.0-alpha.3" and update. You should get it :) – Baumi Jun 17 '16 at 21:38
  • @baumi You're right. I thought angular rc2 would get me that right router with everything at version 2. Apparently I was wrong. Thanks for pointing that out. – Sam Jun 17 '16 at 22:24
  • The last version is 3.0.0-alpha.7 now.Thanks for the hint @Baumi I got everything to work now – Adam S Jun 18 '16 at 14:32
  • @Baumi Do you mind taking a look at http://stackoverflow.com/questions/37897273/authguard-doesnt-wait-for-authentication-to-finish-before-checking-user ? I guess I got the concept, but I've no idea how to turn angularfire's auth into an Observable. Basically I don't know what should my authservice return. Thanks! – Andrew Jun 18 '16 at 22:49
  • How to redirect to the login page, if an Observable is return false ? – Efriandika Pratama Jun 22 '16 at 05:58
  • @Baumi, what is authservice? it is never defined – James Freund Oct 05 '16 at 20:45
  • @JamesFreund - it should be userService that is injected in constructor. Thanks for pointing that out. – Baumi Oct 05 '16 at 21:17