8

Calling `next` of rxjs` Subject` cancels the call http

 2 years ago
source link: https://www.codesd.com/item/calling-next-of-rxjs-subject-cancels-the-call-http.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Calling `next` of rxjs` Subject` cancels the call http

advertisements

I have an application where to logout a user, I have to hit a REST endpoint. In my angular 2 frontend, I have the following service which handles this:

@Injectable()
export class AuthService {

  private authEvents: Subject<boolean>;

  constructor(private http: Http) {
    this.authEvents = new Subject<boolean>();
  }

  login(email: string, password: string): Observable<Response> {
    const url = 'rest/auth/login';
    const body = {
      email: email,
      password: password
    };
    return this.http.post(url, body)
      .do((res: Response) => {
        localStorage.setItem('currentUser', JSON.stringify(res.json()));
        this.authEvents.next(true);
      });
  }

  logout(): Observable<Response> {
    const url = 'rest/auth/logout';
    return this.http.post(url, {})
      .do((res: Response) => {
        localStorage.removeItem('currentUser');
        console.log('hereee!');
        this.authEvents.next(false);
      });
  }

  isSignedIn(): boolean {
    return localStorage.getItem('currentUser') !== null;
  }

  get events(): Observable<boolean> {
    return this.authEvents;
  }

}

Let me explain what I am trying to do here. I have a authEvents subject which I subscribe to in my component like so:

ngOnInit() {
  this.isSignedIn = this.authService.isSignedIn();
  this.authService.events.subscribe(() => {
    this.isSignedIn = this.authService.isSignedIn();
  });
}

When the user clicks logout button, I call the following function in this component:

logout() {
  this.authService.logout()
    .subscribe(() => {
      this.router.navigate(['/home']);
    }, e => this.handleError(e));
}

The problem is that the browser redirects to home even when the call to logout is not yet complete. So effectively, sometimes the frontend successfully logs out, and sometimes it doesn't! What am I doing wrong here?

Note: the session on backend always logs out since the http call does happen. I would also like to see how to use Observables (and rxjs in general) to achieve this, although if too much trouble then Promises can be used.


With Subject you have the case, that emissions are only received by subscribers and not by future subscribers. My guess is that this is your issue here - in order to make your life easier, you could use a BehaviorSubject, which re-emits its current value to any future subscriber as well, so you don't have to worry about any racing-conditions.

However: Strictly seen it will not be an event any more, but more of a state, which is what I would suggest to use anyways in your case - since with RxJS you can leverage the power of states over events.

@Injectable()
export class AuthService {

  public currentUser$: BehaviorSubject<IUser> = new BehaviorSubject(null); // initial state is "null" => user is not logged in
  private isSignedIn$: Observable<boolean> = currentUser$.map(user => Boolean(user)).share();

  constructor(private http: Http) {
    this.authEvents = new Subject<boolean>();
  }

  login(email: string, password: string): Observable<Response> {
    const url = 'rest/auth/login';
    const body = {
      email: email,
      password: password
    };
    return this.http.post(url, body)
      .do((res: Response) => {
        localStorage.setItem('currentUser', JSON.stringify(res.json()));
        this.currentUser$.next(res.json()); // will automatically trigger the logged-in state as well
      });
  }

  logout(): Observable<Response> {
    const url = 'rest/auth/logout';
    return this.http.post(url, {})
      .do((res: Response) => {
        localStorage.removeItem('currentUser');
        console.log('hereee!');
        this.currentUser$.next(null); // will automatically trigger the logged-in state as well
      });
  }
}


In your component

private desotryed$: Subject<boolean> = new Subject();

ngOnInit() {
  this.authService.isSignedIn$
    .takeUntil(this.desotryed$) // to prevent memory leaks through a perpetual subscription
    .subscribe(isSignedIn => {
      this.isSignedIn = isSignedIn;
    });
}

ngOnDestroy() {
  this.desotryed$.next(true);
}

Since the RxJS5 docs are not including the BehaviorSubject yet, here is a link to the old docs: https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/behaviorsubject.md


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK