RxJS shareReplay() does not emit updated value

情到浓时终转凉″ 提交于 2020-08-10 19:19:47

问题


I have a user service which allows login, logout and maintains data about the currently logged in user:

user$ = this.http.get<User>('/api/user')
            .pipe(
              shareReplay(1),
            );

I am using shareReplay(1) because I do not want the webservice to be called several times.

On one of the components, I have this (for simplicity), but I have several other things I want to do if a user is logged in:

<div *ngIf="isUserLoggedIn$ | async">Logout</div>
isUserLoggedIn$ = this.userService.user$
                    .pipe(
                      map(user => user != null),
                      catchError(error => of(false)),
                    );

However, the isLoggedIn$ does not change after the user logs in or logs out. It does change when I refresh the page.

Here's my logout code:

logout() {
  console.log('logging out');
  this.cookieService.deleteAll('/');

  this.user$ = null;

  console.log('redirecting');
  this.router.navigateByUrl('/login');
}

I understand that the internal observable is not reset if I assign the variable to null.

So, for logout, I took clue from this answer: https://stackoverflow.com/a/56031703 about refreshing a shareReplay(). But, the user$ being used in the templates causes my application to go into a tizzy as soon as I attempt to logout.

In one of my attempts, I tried BehaviorSubject:

user$ = new BehaviorSubject<User>(null);

constructor() {
  this.http.get<User>('/api/user')
    .pipe(take(1), map((user) => this.user$.next(user))
    .subscribe();
}

logout() {
  ...
  this.user$.next(null);
  ...
}

This works a little better except when I refresh the page. The auth-guard (CanActivate) always gets the user$ as null and redirects to the login page.

This seemed like an easy thing to do when I started out, but I am going on falling into a deeper hole with each change. Is there a solution to this?


回答1:


For scenarios like this, I use a data stream (user) with an action stream (log user in) and use combineLatest to merge the two:

  private isUserLoggedInSubject = new BehaviorSubject<boolean>(false);
  isUserLoggedIn$ = this.isUserLoggedInSubject.asObservable();

  userData$ = this.http.get<User>(this.userUrl).pipe(
    shareReplay(1),
    catchError(err => throwError("Error occurred"))
  );

  user$ = combineLatest([this.userData$, this.isUserLoggedIn$]).pipe(
    map(([user, isLoggedIn]) => {
      if (isLoggedIn) {
        console.log('logged in user', JSON.stringify(user));
        return user;
      } else {
        console.log('user logged out');
        return null;
      }
    })
  );

  constructor(private http: HttpClient) {}

  login(): void {
    this.isUserLoggedInSubject.next(true);
  }

  logout(): void {
    this.isUserLoggedInSubject.next(false);
  }

I have a working stackblitz here: https://stackblitz.com/edit/angular-user-logout-deborahk

The user$ is the combination of the user data stream and the isUserLoggedIn$ stream that emits a boolean value. That way we can use a map and map the returned value to the user OR to a null if the user has logged out.

Hope something similar works for you.



来源:https://stackoverflow.com/questions/62934781/rxjs-sharereplay-does-not-emit-updated-value

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!