RxJS shareReplay() 不发出更新值
RxJS shareReplay() does not emit updated value
我有一个用户服务,允许登录、注销并维护有关当前登录用户的数据:
user$ = this.http.get<User>('/api/user')
.pipe(
shareReplay(1),
);
我正在使用 shareReplay(1)
因为我不想多次调用网络服务。
在其中一个组件上,我有这个(为简单起见),但如果用户已登录,我还想做其他几件事:
<div *ngIf="isUserLoggedIn$ | async">Logout</div>
isUserLoggedIn$ = this.userService.user$
.pipe(
map(user => user != null),
catchError(error => of(false)),
);
但是,isLoggedIn$
在用户登录或注销后不会改变。当我刷新页面时它确实发生了变化。
这是我的注销代码:
logout() {
console.log('logging out');
this.cookieService.deleteAll('/');
this.user$ = null;
console.log('redirecting');
this.router.navigateByUrl('/login');
}
我知道如果我将变量赋值给 null,内部可观察对象不会被重置。
所以,对于注销,我从这个答案中得到了线索: 关于刷新 shareReplay()
。但是,模板中使用的 user$ 导致我的应用程序在我尝试注销时立即陷入混乱。
在我的一次尝试中,我尝试了 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);
...
}
除了我刷新页面时,这会稍微好一点。 auth-guard (CanActivate
) 始终将 user$ 获取为 null 并重定向到登录页面。
刚开始时,这似乎是一件容易的事,但随着每次更改,我都会陷入更深的困境。有解决办法吗?
对于这样的场景,我使用数据流(用户)和操作流(登录用户)并使用 combineLatest
合并两者:
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);
}
我这里有一个有效的 stackblitz:https://stackblitz.com/edit/angular-user-logout-deborahk
user$
是用户数据流和发出布尔值的 isUserLoggedIn$
流的组合。这样我们就可以使用映射并将返回值映射到用户,或者如果用户已注销则映射到 null。
希望类似的东西对你有用。
我终于能够解决我的问题了。我明白这是 A & B 问题 的最好例子。对于一个问题 A 我认为解决方案是 B 并开始研究它,然而,解决方案实际上是 C.
我希望我能比 1.5 年前更好地理解 RxJS。我把我的答案放在这里是为了帮助别人。
总而言之,我的要求很简单 - 如果用户登陆我的 angular 应用程序,AuthGuard 应该能够使用 cookie 获取和识别用户。如果 cookie 已过期,则应将用户重定向到登录页面。
我认为这是一个很常见的场景,而 RxJS 是解决这个问题的好方法。
下面是我现在的实现方式:
api /api/user
向服务器发送请求。服务器使用 cookie 中的身份验证令牌来识别用户。
这可能导致两种情况:
- cookie 仍然有效,服务器 return 的配置文件
用户的数据。我把这个保存在一个私有成员中以备后用。
private userStoreSubject = new BehaviorSubject<User | null>(null);
- cookie 已过期,服务器无法访问
获取数据(服务器抛出 401 未授权错误)。
以下是检索用户个人资料的方式:
private userProfile$ = this.http.get<User>('/api/user').pipe(
switchMap((user) => {
this.userStoreSubject.next(user);
return this.userStoreSubject;
}),
catchError(() => throwError('user authentication failed')),
);
请注意,如果服务器 return 发生错误(即 401),它会在 catchError()
中被捕获,然后使用 throwError()
再次抛出错误。这对下一步很有帮助。
现在,由于我们知道如何从服务器获取用户配置文件并有一个 BehaviorSubject
来保存当前活动的用户,我们可以使用它来创建一个成员以使用户可用。
user$ = this.userStoreSubject.pipe(
switchMap((user) => {
if (user) {
return of(user);
}
return this.userProfile$;
}),
catchError((error) => {
console.error('error fetching user', error);
return of(null);
}),
);
请注意 switchMap()
的使用,因为我们 return 是一个可观察对象。所以,上面的代码简单地归结为:
- 阅读
userStoreSubject
- 如果用户不是
null
,return 用户
- 如果用户为空,return
userProfile$
(这意味着将从服务器获取配置文件)
- 如果有错误(来自
userProfile$
),return null
.
这使我们能够通过观察来检查用户是否已登录:
isUserLoggedIn$ = this.userStoreSubject.pipe(
map((user) => !!user),
);
请注意,这是从 userStoreSubject
而不是 user$
读取的。这是因为我不想在尝试查看用户是否登录时触发服务器读取。
注销功能也被简化了。我需要做的就是让用户存储为空并删除 cookie。删除 cookie 很重要,否则获取配置文件将再次检索同一用户。
logout() {
this.cookieService.delete('authentication', '/', window.location.hostname, window.location.protocol === 'https:', 'Strict');
this.userStoreSubject.next(null);
this.router.navigate(['login']);
}
现在,我的 AuthGuard 看起来像这样:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.userService.user$
.pipe(
take(1),
map((user) => {
if (!user) {
return this.getLoginUrl(state.url, user);
}
return true;
}),
catchError((error) => {
console.error('error: ', error);
return of(this.getLoginUrl(state.url, null));
})
);
}
我有一个用户服务,允许登录、注销并维护有关当前登录用户的数据:
user$ = this.http.get<User>('/api/user')
.pipe(
shareReplay(1),
);
我正在使用 shareReplay(1)
因为我不想多次调用网络服务。
在其中一个组件上,我有这个(为简单起见),但如果用户已登录,我还想做其他几件事:
<div *ngIf="isUserLoggedIn$ | async">Logout</div>
isUserLoggedIn$ = this.userService.user$
.pipe(
map(user => user != null),
catchError(error => of(false)),
);
但是,isLoggedIn$
在用户登录或注销后不会改变。当我刷新页面时它确实发生了变化。
这是我的注销代码:
logout() {
console.log('logging out');
this.cookieService.deleteAll('/');
this.user$ = null;
console.log('redirecting');
this.router.navigateByUrl('/login');
}
我知道如果我将变量赋值给 null,内部可观察对象不会被重置。
所以,对于注销,我从这个答案中得到了线索:shareReplay()
。但是,模板中使用的 user$ 导致我的应用程序在我尝试注销时立即陷入混乱。
在我的一次尝试中,我尝试了 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);
...
}
除了我刷新页面时,这会稍微好一点。 auth-guard (CanActivate
) 始终将 user$ 获取为 null 并重定向到登录页面。
刚开始时,这似乎是一件容易的事,但随着每次更改,我都会陷入更深的困境。有解决办法吗?
对于这样的场景,我使用数据流(用户)和操作流(登录用户)并使用 combineLatest
合并两者:
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);
}
我这里有一个有效的 stackblitz:https://stackblitz.com/edit/angular-user-logout-deborahk
user$
是用户数据流和发出布尔值的 isUserLoggedIn$
流的组合。这样我们就可以使用映射并将返回值映射到用户,或者如果用户已注销则映射到 null。
希望类似的东西对你有用。
我终于能够解决我的问题了。我明白这是 A & B 问题 的最好例子。对于一个问题 A 我认为解决方案是 B 并开始研究它,然而,解决方案实际上是 C.
我希望我能比 1.5 年前更好地理解 RxJS。我把我的答案放在这里是为了帮助别人。
总而言之,我的要求很简单 - 如果用户登陆我的 angular 应用程序,AuthGuard 应该能够使用 cookie 获取和识别用户。如果 cookie 已过期,则应将用户重定向到登录页面。
我认为这是一个很常见的场景,而 RxJS 是解决这个问题的好方法。
下面是我现在的实现方式:
api /api/user
向服务器发送请求。服务器使用 cookie 中的身份验证令牌来识别用户。
这可能导致两种情况:
- cookie 仍然有效,服务器 return 的配置文件 用户的数据。我把这个保存在一个私有成员中以备后用。
private userStoreSubject = new BehaviorSubject<User | null>(null);
- cookie 已过期,服务器无法访问 获取数据(服务器抛出 401 未授权错误)。
以下是检索用户个人资料的方式:
private userProfile$ = this.http.get<User>('/api/user').pipe(
switchMap((user) => {
this.userStoreSubject.next(user);
return this.userStoreSubject;
}),
catchError(() => throwError('user authentication failed')),
);
请注意,如果服务器 return 发生错误(即 401),它会在 catchError()
中被捕获,然后使用 throwError()
再次抛出错误。这对下一步很有帮助。
现在,由于我们知道如何从服务器获取用户配置文件并有一个 BehaviorSubject
来保存当前活动的用户,我们可以使用它来创建一个成员以使用户可用。
user$ = this.userStoreSubject.pipe(
switchMap((user) => {
if (user) {
return of(user);
}
return this.userProfile$;
}),
catchError((error) => {
console.error('error fetching user', error);
return of(null);
}),
);
请注意 switchMap()
的使用,因为我们 return 是一个可观察对象。所以,上面的代码简单地归结为:
- 阅读
userStoreSubject
- 如果用户不是
null
,return 用户 - 如果用户为空,return
userProfile$
(这意味着将从服务器获取配置文件) - 如果有错误(来自
userProfile$
),returnnull
.
这使我们能够通过观察来检查用户是否已登录:
isUserLoggedIn$ = this.userStoreSubject.pipe(
map((user) => !!user),
);
请注意,这是从 userStoreSubject
而不是 user$
读取的。这是因为我不想在尝试查看用户是否登录时触发服务器读取。
注销功能也被简化了。我需要做的就是让用户存储为空并删除 cookie。删除 cookie 很重要,否则获取配置文件将再次检索同一用户。
logout() {
this.cookieService.delete('authentication', '/', window.location.hostname, window.location.protocol === 'https:', 'Strict');
this.userStoreSubject.next(null);
this.router.navigate(['login']);
}
现在,我的 AuthGuard 看起来像这样:
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
return this.userService.user$
.pipe(
take(1),
map((user) => {
if (!user) {
return this.getLoginUrl(state.url, user);
}
return true;
}),
catchError((error) => {
console.error('error: ', error);
return of(this.getLoginUrl(state.url, null));
})
);
}