1

i have trouble understanding how this works. Every article i read always do this stuff. But it is always async that the class variable will only be set when the callback is executed.

So how do you properly set the class variable with data from server side.

Dont get me wrong, i completely understand as to why the value of this.isAuthorized is that way. its just that im at a lost as to how i would set my class variable this.isAuthorized properly.

Please see sample code below;

export class AuthService {
  private apiUrl = environment.api_uri;
  public isAuthorized: boolean = false;

  constructor(private http: HttpClient) { }

  public authorize(): void {
    this.http.get(this.apiUrl+'/auth')
             .subscribe(
               data => {
                this.isAuthorized = true;
                console.log("authorize => "+this.isAuthorized) //displays true
              },
              error => {
                console.log(error);
                console.log("authorize => "+this.isAuthorized) //displays false
              });
    console.log("authorize => "+this.isAuthorized) //display false
  }
}

Here is where i use the above service

export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router){}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    this.auth.authorize();

    console.log( this.auth.isAuthorized ); // dksplays false

    if( this.auth.isAuthorized ) return true;

    return this.router.parseUrl("/403");
  }

}

1 Answer 1

3

One of Angular's pillars is RxJS. So, we need to change the way we think about services, methods, variables.

Let's talk about your service

AuthService makes a call to your backend and figure out if the user is authenticated. AuthGuard uses this service to do something. As you can see from syntax of canActivate, Angular expects you to return one of the following types

Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree

What AuthGuard returns now is boolean | UrlTree. You can also return Observable<boolean | UrlTree>. This should tell us something. AuthService has an internal Observable however, it subscribes to it and does not return it. So let's do some refactor

export class AuthService {
  private apiUrl = environment.api_uri;

  constructor(private http: HttpClient) { }

  public authorize(): Observable<boolean> {
    return this.http.get(this.apiUrl+'/auth')
             .pipe(
               map(data => {
                 // some logic here
                 let authorized = ....
                 return authorized;
              }),
              catchError(error => {
                 /** do some error handling
                  * I assume you wouldn't activate routing in case of an error
                  * Also, you need to wrap your return value in an `Observable`
                  * of is the simplest way to do that
                  */
                 return of(false);
              }));
  }
}

Now that AuthService returns an Observable we can use some operators on top of that and return it from AuthGuard.

export class AuthGuard implements CanActivate {
  constructor(private auth: AuthService, private router: Router){}

  canActivate(
    next: ActivatedRouteSnapshot,
    state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
    return this.auth.authorize().pipe(
       map(isAuthorized => isAuthorized || this.router.parseUrl("/403"))
    );
  }

}

What I just suggested made your service stateless which means it does not hold any data. It just does whatever it does again every time someone calls one of its methods.

Sometimes, our services needs to hold some data and new comers should get this old retrieved data. In this case, we can use Subject, BehaviorSubject or shareReplay operator (which is my favorite).

Let's talk about Subject.

For more info, you can read it here

What is a Subject? An RxJS Subject is a special type of Observable that allows values to be multicasted to many Observers. While plain Observables are unicast (each subscribed Observer owns an independent execution of the Observable), Subjects are multicast.

So let's create a Subject (from the docs)

const subject = new Subject<number>();

const subscriber1 = subject.subscribe(val => console.log(val))
const subscriber2 = subject.subscribe(val => console.log(val))

// this will trigger callback functions of the subscribes above
subject.next(5);

However, late comers won't get the value 5 (last emitted value) following console.log statement won't execute

const subscriber3 = subject.subscribe(val => console.log(val))

If you care about new comers to be able to retrieve what was emitted before, you can simply use BehaviorSubject

For more info, you can read it here


const subject = new BehaviorSubject(123);

// two new subscribers will get initial value => output: 123, 123
subject.subscribe(console.log);
subject.subscribe(console.log);

// two subscribers will get new value => output: 456, 456
subject.next(456);

// new subscriber will get latest value (456) => output: 456
subject.subscribe(console.log);

// all three subscribers will get new value => output: 789, 789, 789
subject.next(789);

This is pretty useful if you should be able to update the "source" later. However, in some cases, different components need the same data and it never changes and you only want to make the http call once. Like the user begin authenticated (in your case).

Using BehaviorSubject will require some initial value and your subscribers will get this value as well. You may not want that. Instead, let's use shareReplay

Let's refactor AuthService a bit.

I'll create a class member called isAuthorized (an Observable)

I'll use the same logic from above and since it is an Observable, it will not make the call until someone subscribes to it.

And finally, I'll add a shareReplay operator to make sure that this request will made only once.

export class AuthService {
  private apiUrl = environment.api_uri;
  private isAuthorized = this.http.get(this.apiUrl+'/auth')
             .pipe(
               map(data => {
                 // some logic here
                 let authorized = ....
                 return authorized;
              }),
              catchError(error => {
                 return of(false);               
              }),
              shareReplay()

  );

  constructor(private http: HttpClient) { }

}

Now you can use it as follows and no matter how many times you use it, the underlying http call will be executed only once.

authService.isAuthorized.subscribe(_ => .....)
Sign up to request clarification or add additional context in comments.

9 Comments

yes, i am having trouble changing the way i look at services. i was thinking that services will conenct to backend ang process that, put it in some variable or return the raw data (not the obersable), and the user will use the data returned by the service. Thats who i look at services :(.
As a rule of thumb, you should never subscribe within a service which make it almost impossible for consumers of this service to use that data. However, some services may need to hold some state which can be achieved by RxJS. I'll add that to my answer
ohhh so basically, services contains the API call, and always returns the obervable, and let the consumer of the service to subscribe to it?.
Yes exactly and since Observables are lazy, until someone subscribes, the http call will never be made
I see, i am looking at services the wrong way. It has a somewhat different meaning to some extent in backend (ie spring)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.