8

I have a route guard like below

@Injectable()
export class AuthGuard implements CanActivate {

constructor(private router: Router, private authenticationSvc: AuthenticationService) { }

canActivate(): Observable<boolean> {
    return this.authenticationSvc.getAuthenticatedUser().map(
        r => {
            if (this.authenticationSvc.isAuthenticated()) {
                // logged in so return true
                return true;
            }
            this.router.navigateByUrl('/login');
            return false;
        })
}

The issue is that sometimes getAuthenticatedUser returns a 401, and I have an http-interceptor that handles the 401 and redirect to the login page. The issue is that this .map never resolves because the http request throws an error, and the angular router gets stuck on this first routing request and can't handle the subsequent request from the interceptor. How can I handle this error and have the Observable returned resolve to false and keep things moving?

  getAuthenticatedUser() {
         let getUserObservable = this.http.get(ApiUrl + 'security/getAuthenticatedUser')
            .map((res: any) => res.json())
            .share()

        //Get the result for the cache
        getUserObservable.subscribe(
            r => {
                if (r.success) {
                    this.authenticatedUser = r.result.user;
                }
            }); 

        //return the observable
        return getUserObservable;
  } 

and http-intercepter below

export class HttpInterceptor extends Http {
    authSvc: AuthenticationService;
    lastClicked: any = 0;
    constructor(backend: ConnectionBackend, defaultOptions: RequestOptions, private _router: Router, private injector: Injector) {
        super(backend, defaultOptions);
    }
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.request(url, options));
}

get(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.get(url, options));
}

post(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.post(url, body, this.getRequestOptionArgs(options)));
}

put(url: string, body: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.put(url, body, this.getRequestOptionArgs(options)));
}

delete(url: string, options?: RequestOptionsArgs): Observable<Response> {
    return this.intercept(super.delete(url, options));
}

getRequestOptionArgs(options?: RequestOptionsArgs): RequestOptionsArgs {
    if (options == null) {
        options = new RequestOptions();
    }
    if (options.headers == null) {
        options.headers = new Headers();
    }
    options.headers.append('Content-Type', 'application/json');
    return options;
}

 intercept(observable: Observable<Response>): Observable<Response> {
    return observable.catch((err, source) => {
        //If we get a 401 from the api that means out FormsAuthenticationTicket has expired, clear the auth cookie and navigate back to terms page
        if (err.status == 401) {
            this._router.navigateByUrl('/login');
        }

        return Observable.throw(err);
    });
}
Josh
  • 1,648
  • 8
  • 27
  • 58
  • Shouldn't you then/catch the response from getAuthenticatedUser? http://stackoverflow.com/questions/23559341/using-success-error-finally-catch-with-promises-in-angularjs – Denise Mauldin Mar 01 '17 at 23:08
  • show me what you mean, added http-intercepter and getAuthenticatedUser() for reference – Josh Mar 01 '17 at 23:16

1 Answers1

9

You can catch errors and return Observable<bool> as follows:

@Injectable()
export class AuthGuard implements CanActivate {

constructor(private router: Router, private authenticationSvc: AuthenticationService) { }

canActivate(): Observable<boolean> {
    return this.authenticationSvc.getAuthenticatedUser().map(
        r => {
            if (this.authenticationSvc.isAuthenticated()) {
                // logged in so return true
                return true;
            }
            this.router.navigateByUrl('/login');
            return false;
        })
        .catch((error: any) => {
            this.router.navigateByUrl('/login');
            return Observable.of(false);
        });
}
seidme
  • 12,543
  • 5
  • 36
  • 40
  • This is the first thing I tried, but it never hits the catch – Josh Mar 01 '17 at 23:12
  • Then your problem is with the interceptor, most probably. – seidme Mar 01 '17 at 23:18
  • I don't think so, all it does is call router.navigate and then throw the original error, I added it to the question contents – Josh Mar 01 '17 at 23:23
  • I think the issue with what you are proposing is that the original observable has already been returned from this function, so you can't just return a new one in the catch – Josh Mar 01 '17 at 23:24
  • Please see the accepted answer: http://stackoverflow.com/questions/41911094/error-in-canactivate-guard-method – seidme Mar 01 '17 at 23:44
  • This works undoubtedly, and as I said already, your problem most probably lies in the interceptor. Try excluding it completely and then give it a try. – seidme Mar 01 '17 at 23:52
  • 1
    You are right .catch does work undoubtedly. I updated the question again, I had the wrong example for getAuthenticatedUser(). Actually I am sharing the observable and pulling the user out in the service, and I was missing the error handler on that subscription. Added it and everything is flowing as expected now. Thanks! – Josh Mar 02 '17 at 00:05
  • It does bring up a question though. I didn't think it was "necessary" to handle errors for all subscriptions, especially if there isn't any action necessary. What is the best practice for an empty error handler? Right now I just have e => {} – Josh Mar 02 '17 at 00:12
  • It does something - catches the error in order to return the Observable, or you're talking about some other 'catch' block? – seidme Mar 02 '17 at 00:23
  • In my original question, the issue is in getAuthenticatedUser() getUserObservable.subscribe( r => { if (r.success) { this.authenticatedUser = r.result.user; } }); Doesn't have an error handler block. I changed it to getUserObservable.subscribe( r => { if (r.success) { this.authenticatedUser = r.result.user; } }, e => {}); To fix the issue. So yes talking about a different catch block. – Josh Mar 02 '17 at 17:09