Search code examples
angularrxjsangular-routerangular-httpclientangular-router-guards

Wait for http inside Guard on Angular 5


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));
}

Solution

  • 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)
            });
    }