9

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
  • 27,293
  • 11
  • 59
  • 73
celsomtrindade
  • 4,501
  • 18
  • 61
  • 116

2 Answers2

16

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)
        });
}
Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
  • 1
    hi , can u please explain your code , like why using map in place of subscribe , using of() and other such things. – charle819 May 31 '18 at 07:06
  • 2
    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 – Jota.Toledo May 31 '18 at 09:01
  • 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 ? – charle819 May 31 '18 at 12:56
  • "various cases" is too vague. Depends on what you are actually doing. Feel free to open a new question and tag me on it. – Jota.Toledo May 31 '18 at 14:05
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;
        });
    }
Jota.Toledo
  • 27,293
  • 11
  • 59
  • 73
El houcine bougarfaoui
  • 35,805
  • 9
  • 37
  • 37
  • why first() at the end? – Jota.Toledo Nov 09 '17 at 21:07
  • Your code is just missing "from" on the Observable import, but it's working. Also, on the [Angular 5 release blog post](https://blog.angular.io/version-5-0-0-of-angular-now-available-37e414935ced) 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 (?) – celsomtrindade Nov 10 '17 at 09:56
  • @celsomtrindade read the article carefully and complete, there is a difference on how to use the elements from 'rxjs/operators' – Jota.Toledo Nov 10 '17 at 13:22