Wait for http inside Guard on Angular 5

13,343

Solution 1

So, first thing first: avoid the use of any when possible, specially when you know which type belongs where.

export interface FooInterface {
  status: string;
  fooString : string;
  fooNumber : number;
}

First Ill define the interface of the service in a clean manner, and then Ill refactor the guard class.

UPDATED ANSWER for rxjs 6.x

import { throwError, Observable } from 'rxjs';
import { catchError } from 'rxjs/operators';

@Injectable()
export class FooService {
   constructor(private _http: HttpClient) {}

   public apiGet(url: string): Observable<FooInterface> {
    return this._http
        .get<FooInterface>(this.apiUrl + url)
        .pipe(
          catchError(error => {
             // do general error handling if necessary and throw
            throwError(error);
           })
        );
  }
}

The guard class:

import { of, Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';

@Injectable()
export class ResolveGuard implements CanActivate {
constructor(
    private fooService: FooService ,
) { }

canActivate(): Observable<boolean> {
    return this.fooService.apiGet('my/url')
       .pipe(
         map(response => response.status === 'success'),
         catchError(error => of(false))
       );
}

ORIGINAL ANSWER for rxjs 5.x

import { _throw } from 'rxjs/observable/throw':

constructor(private _http: HttpClient) {}

public apiGet(url: string): Observable<FooInterface> {
    return this._http
        .get<FooInterface>(this.apiUrl + url)
        .catch(error => {
          // do general error handling if necessary and throw
          _throw(error);
       });
}

The guard class:

import { of } from 'rxjs/observable/of';

@Injectable()
export class ResolveGuard implements CanActivate {
constructor(
    private _api: ApiService,
) { }

canActivate(): Observable<boolean> {
    return this._api.apiGet('my/url')
       .map(response => {
          let val = false;
          if ( response.status === 'success') {
                // Consume data here
                val = true;
          }
          return val;
        }).catch(error => {
          // consume the error maybe?
          of(false)
        });
}

Solution 2

just import map operator and it will work :

import { Observable } "rxjs/Observable"; 
import "rxjs/add/operator/map"; 

 canActivate(): Observable<boolean>{
        return this._api.apiGet('my/url').map(response => {
            if ( response.status === 'success') {
                // Consume data here
                return true;
            }
            return false;
        });
    }
Share:
13,343
celsomtrindade
Author by

celsomtrindade

Amante dos códigos. Fascinado pela mágica digital. "Nunca subestime a capacidade de um usuário. Ele irá quebrar o mais simples dos códigos, com o mais simples uso de interface."

Updated on July 24, 2022

Comments

  • celsomtrindade
    celsomtrindade over 1 year

    I'm using a Guard on an Angular application to resolve initial critical data. On the version 4 of Angular I was duing it like this:

    // app.routing.ts
    routing = [{
        path: '', component: AppComponent, canActivate: [ResolveGuard],
    }];
    
    // resolve.guard.ts
    @Injectable()
    export class ResolveGuard implements CanActivate {
        constructor(
            private _api: ApiService,
        ) { }
    
        canActivate(): any {
            return this._api.apiGet('my/url').map(response) => {
                if ( response.status === 'success') {
                    // Consume data here
                    return true;
                }
    
                return false;
            }).first();
        }
    }
    

    Since the new version of Http on Angular 5 doesn't use the .map() property anymore, this is not working.

    If I change .map() to .subscribe() it doesn't throw any errors, but the application never resolve properly. On the other hand, using .first() and/or .map() throw some errors, as expected in this version.

    What should I do in this case?

    I need to activate that route only if and when the initial data is loaded.


    Edit to add info about the apiGet function:

    constructor(private _http: HttpClient) {}
    
    public apiGet(url: string): any {
        return this._http
            .get(this.apiUrl + url)
            .catch(this.handleError.bind(this));
    }
    
  • Jota.Toledo
    Jota.Toledo over 6 years
    why first() at the end?
  • celsomtrindade
    celsomtrindade over 6 years
    Your code is just missing "from" on the Observable import, but it's working. Also, on the Angular 5 release blog post it says I should import the map like this: import { map } from 'rxjs/operators'; but it's not working. It just work when I import the old way. Maybe this is what was wrong with my code (?)
  • Jota.Toledo
    Jota.Toledo over 6 years
    @celsomtrindade read the article carefully and complete, there is a difference on how to use the elements from 'rxjs/operators'
  • charle819
    charle819 almost 6 years
    hi , can u please explain your code , like why using map in place of subscribe , using of() and other such things.
  • Jota.Toledo
    Jota.Toledo almost 6 years
    Internally, angular subscribes to the observable returned by the guard. For that reason, the only thing that the guard needs to do is to transform the observable returned by the api service into a boolean stream. In the case that the source observable, the HTTP request, fails,the guard still has to return a stream, not throw. Thats why I use of to map the error to a new stream. @charle819
  • charle819
    charle819 almost 6 years
    oh ok , but there are various cases where if we try to call a method from inside the AuthGuard (which basically calls a service) , the AuthGuard does not seems to wait for the response of the service, can u tell why ?
  • Jota.Toledo
    Jota.Toledo almost 6 years
    "various cases" is too vague. Depends on what you are actually doing. Feel free to open a new question and tag me on it.