我正在创建一个使用 WebSocket 连接与后端服务器通信的 Angular 应用程序。登录服务器时,会发送由不同服务处理的多个信息包,如下所示:
constructor(private socketService: SocketService, private authService: AuthService) {
this.obs$ = this.socketService.get().pipe(filter(data => data[SocketService.CONTROLLER] === 'account'));
this.details$ = this.obs$.pipe(
filter(data => data[SocketService.ACTION] === 'details'),
map(data => data[SocketService.PAYLOAD]),
takeUntil(this.authService.logout),
shareReplay({bufferSize: 1, refCount: true}));
}
这在首次登录时工作正常 - 因此在创建服务时,但是当注销并再次登录时,服务构造函数不会再次触发,并且似乎 Observabledetails$
不会发出任何值。删除refCount
后,即使使用其他帐户登录,也会显示我登录的第一个帐户的详细信息。
这适用于组件中的第一次登录,但不适用于第二次登录。
this.accountService.getDetails().subscribe(d => console.log(d));
该服务由在延迟加载模块中导入的模块提供。解决此问题的最佳实践是什么?我想到的是为每个在登录时调用的服务创建一个 init 方法,但这似乎非常不直观且有点错误。我需要该服务来接收数据,即使它当前未被任何组件使用。
案例 1
takeUntil(this.authService.logout),
shareReplay({bufferSize: 1, refCount: false})
在/other
和/details
之间导航 - 相同的结果,因为refCount: false
不会导致正在使用的ReplaySubject
被销毁,因此当新订阅者注册并且ReplaySubject
将保持不变时,源不会重新订阅
共享重播会保留数据缓存,即使 taketill 发出
。
这是因为takeUntil
的通知程序将在组件被销毁之前发出(即当订阅者取消注册时(。然后,takeUntil
将发出complete
通知,这意味着ReplaySubject
也将complete
:
complete() {
subscription = undefined;
subject!.complete();
},
当ReplaySubject
在完成后收到新订阅者时,将到达这些行,因此新订阅者将收到所有缓存的值和complete
通知。
并且因为shareReplay
的源已经完成,它无法接收任何其他值,因为侦听通知的Subscriber
已经完成,因此它变成了stopped
:
complete(): void {
if (!this.isStopped) {
this.isStopped = true;
this._complete();
}
}
案例2
takeUntil(this.authService.logout),
shareReplay({bufferSize: 1, refCount: true })
在/other
和/details
之间导航 - 只有第一次将数据显示为源时才是SocketService
的Subject
实例;因此,当订阅Subject
实例时,订阅者将被添加到订阅者列表中,以便当Subject
发出值时,所有注册订阅者都将收到它
注销时:
onLogout() {
this.authService.doLogout();
this.router.navigate(['/'])
}
takeUntil
将在订阅者从ReplaySubject
的订阅者列表中删除之前发出(即,当 comp 被销毁时,例如this.router.navigate(['/'])
(。
然后,takeUntil
将发出complete
通知,如下所示:
complete() {
subscription = undefined;
subject!.complete();
},
然后this.router.navigate(['/'])
将导致组件被销毁,并且由于您也使用异步管道,它将取消订阅shareReplay
使用的ReplaySubject
,因此将达到以下行:
if (subscription && useRefCount && refCount === 0) {
subscription.unsubscribe();
subscription = undefined;
subject = undefined;
}
表达式将被false
,因为subscription
已经未定义(来自complete
回调(。
可能的解决方案
auth.service.ts
我认为稍微修改一下此服务可能会很有用,以便能够正确区分登录状态(true
或false
(。
loggedIn = new Subject<boolean>();
doLogin() {
this.loggedIn.next(true);
}
doLogout() {
this.loggedIn.next(false);
}
帐户.服务.ts
this.details$ = this.obs$.pipe(
filter(data => data[SocketService.ACTION] === "details"),
map(data => data[SocketService.PAYLOAD]),
startWith(null),
takeUntil(this.authService.loggedIn.pipe(filter(v => v === false))),
repeatWhen(
completeSbj => completeSbj.pipe(
switchMapTo(this.authService.loggedIn.pipe(filter(v => v === true), take(1))),
)
),
shareReplay({bufferSize: 1, refCount: false })
);
让我们浏览一下每个相关的代码块。
如您所见,我们保留了shareReplay({bufferSize: 1, refCount: false })
,因为我们不希望ReplaySubject
在没有订阅者时被销毁,因此我们不想重新订阅源代码。
有了这个,您可以根据需要浏览路线,但用户的数据将是相同的。
takeUntil(this.authService.loggedIn.pipe(filter(v => v === false))),
我们希望在用户注销时发出完整的通知。
repeatWhen(
completeSbj => completeSbj.pipe(
switchMapTo(this.authService.loggedIn.pipe(filter(v => v === true), take(1))),
)
),
将允许我们在用户注销后再次登录时重新订阅源。因此,每次用户登录时,我们都会得到一个新值(如SocketService
的counter
变量所示(。
最后,当用户登录,然后注销然后再次登录时,startWith(null),
特别有用。如果没有这个运算符,在第二次登录时,用户将看到以前的数据(例如Awesome Data 1
(。但是当使用它时,由于重新订阅是在用户再次登录时完成的(由于this.authService.loggedIn.pipe(filter(v => v === true), take(1))
(,我们将首先将null
作为值,直到SocketService
提供数据。
堆栈闪电战。