问题
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