4

I have a "CanActivate" checking on my web service if the user is logged in.

The "CanActivate" return Observable. The only worry is that I would need to recover the value of this boolean in order to be able to adapt my view if the user is connected or not without redoing a 2nd request...

canActivate(): Observable<boolean>|boolean {
    return this.connected();
  }
public connected() : Observable<boolean>{
    return this.http.get(this.connectedUrl,{ withCredentials: true })
      .map(this.extractBoolean)
      .catch(this.handleError);
  }

Thank you for your help

Jordan R.
  • 59
  • 2
  • 6
  • Every thing you save in user side can be edited by user. So in each page view you should send a new request. – mohammad May 11 '17 at 08:28
  • What is the question? – Daniel Cooke May 11 '17 at 08:32
  • @DanielCooke if i'm not mistaken Jordan has a guard in which he does an ajax call to get a user. After that a component is loaded and in the component he does a second ajax call to get a user. Question: How to do it with just 1 call. – Mike Bovenlander May 11 '17 at 08:43

2 Answers2

1

I had a similar issue, and solved it using a authentication service, where I instead uses a BehaviorSubject, to track the member authentication state:

public member: Subject<Member> = new Subject<Member>();

private _cachedMember: Member;
private _member$: BehaviorSubject<Member>;

constructor(private _http: Http, private _router: Router) {         
}

get member$(): Observable<Member> {     
    if (!this._cachedMember) {          
        this.getAuthorizedMember().subscribe();
    }
    return this._member$.asObservable();
}

private getAuthorizedMember(): Observable<Member> {
     // call your auth API and set _cachedMember accordingly:
     this._cachedMember = member;
     this._member$.next(this._cachedMember);
}

My auth.guard.ts canActivate implementation:

canActivate(): Observable<boolean> | boolean {

        return this._authService.member$.map(member => {
            if (member) return true;
            else {
                // Set entry url
                var currentUrl: URL = new URL(this._document.location.href);
                this._authService.entryUrl = currentUrl.pathname;

                this._router.navigate(['/']);
                return false;
            }
        });

}

The else statement saves the url request, so the member is redirected correctly after login.

Then you only need to call your authentication API if the _cachedMember is null. Remember to set _cachedMember to null on logout and inform subjects.

logout() {
    this._cachedMember = null;
    this._member$.next(this._cachedMember );
}

[EDIT] I found an issue with the earlier solution. Its a good idea to wrap the authorizing mechanism in an observable, and do a null check if the _member$ subject is assign. Then the initial state is valid if navigating directly to a route which is guarded!

DNRN
  • 2,397
  • 4
  • 30
  • 48
1

If you want to save some data when you make a request on your backend to make sure a user is connected, instead of making the HTTP request in the guard, create a service that does this request (which is a good practice anyway and you should do that whether or not you want to save a request).

That said, in your service, which is a singleton, you can simply map your call (which results into an Observable) and save the value in your service.

For example :

@Injectable()
export class SomeService {
  private userData: IUserData;

  constructor(private http: Http) { }

  getUserDataIfConnected() {
    this.http.get('http://some-url')
      .map((res: Response) => res.json() as IUserData)
      .map(userData => this.userData = userData)
  }
}

Then in your guard :

canActivate(): Observable<boolean> {
  return this.someService.getUserDataIfConnected()
    // if there was no error, we don't care about the result, just return true : The user is connected
    .mapTo(true)
    // if the user wasn't logged, your backend shoud return an HTTP error code in the header
    // and angular HTTP service will throw an error
    .catch(err => return Observable.of(false))
}

It doesn't have to be more complex than that :)

maxime1992
  • 22,502
  • 10
  • 80
  • 121