身份验证时重新初始化服务



我正在创建一个使用 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之间导航 - 只有第一次将数据显示为源时才是SocketServiceSubject实例;因此,当订阅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

我认为稍微修改一下此服务可能会很有用,以便能够正确区分登录状态(truefalse(。

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))),
)
),

将允许我们在用户注销后再次登录时重新订阅源。因此,每次用户登录时,我们都会得到一个新值(如SocketServicecounter变量所示(。

最后,当用户登录,然后注销然后再次登录时,startWith(null),特别有用。如果没有这个运算符,在第二次登录时,用户将看到以前的数据(例如Awesome Data 1(。但是当使用它时,由于重新订阅是在用户再次登录时完成的(由于this.authService.loggedIn.pipe(filter(v => v === true), take(1))(,我们将首先将null作为值,直到SocketService提供数据。

堆栈闪电战。

最新更新