如何确定触发Angular Effect执行的具体Signal?
在Angular Effect中如何判断是哪个Signal触发了执行?
我在组件中定义了两个Signal和一个Effect:当isLoggedIn Signal变化时,希望启动服务内的定时器函数;另一个idleTimeoutSignalModel Signal会基于服务定时器的状态变化频繁更新。
目前我通过在服务中设置isTimerRunning布尔值来避免重复执行定时器启动逻辑,但如果能直接判断触发Effect的Signal会更便捷。
我的代码
IdleTimeoutService 服务
import { Injectable, Signal, WritableSignal, signal } from '@angular/core'; import { Observable, Subscription, merge, fromEvent, timer, BehaviorSubject } from 'rxjs'; import { environment } from '../../environments/environment'; import { LocalStorageService } from './storage.service'; export interface IIdleTimeoutModel { expired: boolean; expiring: boolean; } @Injectable({ providedIn: 'root', }) export class IdleTimeoutService { constructor(private localStorageService: LocalStorageService) { } idleTimeout: IIdleTimeoutModel = { expired: false, expiring: false } private idleSignal: WritableSignal<IIdleTimeoutModel> = signal(this.idleTimeout); readonly idleSignalModel: Signal<IIdleTimeoutModel> = this.idleSignal.asReadonly(); private idleEventTriggers: Observable<any> = new Observable(); private timer: Subscription = new Subscription(); private timeOutMilliSeconds: number = 1000; private timeOutWarningMilliSeconds: number = 1000; private isTimerRunning: boolean = false; public startWatching(): void { if (!this.isTimerRunning) { this.timeOutMilliSeconds = environment.idleTimeInMilliseconds; this.timeOutWarningMilliSeconds = environment.idleTimeWarningInMilliseconds; this.idleEventTriggers = merge( fromEvent(document, 'click'), fromEvent(document, 'mousedown'), fromEvent(document, 'keypress') ); this.idleEventTriggers.subscribe((res) => { this.resetTimer(); }); this.startTimer(); } } private startTimer() { this.isTimerRunning = true; // 当前用来避免重复启动定时器的临时方案 let timeoutModel: IIdleTimeoutModel = { expired: false, expiring: false, }; let initDatetime = new Date(); let timeoutSeconds = this.timeOutWarningMilliSeconds / 1000 + initDatetime.getSeconds(); let warningSeconds = this.timeOutMilliSeconds / 1000 + initDatetime.getSeconds(); let expiringDatetime = new Date().setSeconds(timeoutSeconds); let expiredDatetime = new Date().setSeconds(warningSeconds); this.localStorageService.set('expiringDatetime', expiringDatetime.toString()); this.localStorageService.set('expiredDatetime', expiredDatetime.toString()); // 定时器逻辑 this.timer = timer(1000, 5000).subscribe((response) => { let nowDatetime = new Date(); // 检查即将过期时间 let checkExpiringDatetimeValue = this.localStorageService.get('expiringDatetime'); if (checkExpiringDatetimeValue) { if (nowDatetime.getTime() >= parseInt(checkExpiringDatetimeValue)) { this.idleSignal.set({ expiring: true, expired: false }) } else { this.idleSignal.set({ expiring: false, expired: false }) } } else { this.resetTimer(); } // 检查已过期时间 let checkExpiredDatetimeValue = this.localStorageService.get('expiredDatetime'); if (checkExpiredDatetimeValue) { if (nowDatetime.getTime() >= parseInt(checkExpiredDatetimeValue)) { if (timeoutModel.expired === false) { timeoutModel.expired = true; this.idleSignal.set({ expiring: false, expired: true }) } } else { if (timeoutModel.expired === true) { timeoutModel.expired = false; this.idleSignal.set({ expiring: false, expired: true }) } } } else { this.resetTimer(); } }); } public resetTimer() { this.timer.unsubscribe(); this.idleSignal.set({ expiring: false, expired: false }) this.startTimer(); } public stopTimer() { this.isTimerRunning = false; this.timer.unsubscribe(); } }
组件代码(AppComponent)
// 组件中声明的变量 isLoggedIn = this.appService.isLoggedInSignal; // 用户登录时会更新的Signal private idleTimeoutSignalModel = this.idleTimeoutService.idleSignalModel; // 随定时器状态变化的Signal // 构造函数中的Effect effect(() => { if (this.isLoggedIn() && environment.idleTimeout) { const idleModel = this.idleTimeoutSignalModel(); this.idleTimeoutService.startWatching(); // 希望只在isLoggedIn变化时执行此函数 if (idleModel.expiring) { this.dialogService.openTimeOutDialog('延长会话?', '由于长时间未操作,您的登录会话即将过期。是否继续?'); } else { this.dialogService.closeDialogById('timeout-dialog'); } if (idleModel.expired) { try { this.broadcastService.publish({ type: 'mnsso-logout', payload: 'true', }); } catch { console.log('广播错误'); } this._authService.logout(); } } });
解决方案
方案1:拆分Effect(推荐)
最清晰的做法是把两个独立的逻辑拆分成两个Effect,让每个Effect只响应自己关心的Signal,完全避免判断触发源的需求:
// 处理登录状态变化的Effect:仅在isLoggedIn变化时执行 effect(() => { if (environment.idleTimeout) { if (this.isLoggedIn()) { this.idleTimeoutService.startWatching(); } else { // 用户登出时停止定时器 this.idleTimeoutService.stopTimer(); } } }); // 处理超时状态的Effect:仅在idleTimeoutSignalModel变化时执行 effect(() => { if (!this.isLoggedIn() || !environment.idleTimeout) return; const idleModel = this.idleTimeoutSignalModel(); if (idleModel.expiring) { this.dialogService.openTimeOutDialog('延长会话?', '由于长时间未操作,您的登录会话即将过期。是否继续?'); } else { this.dialogService.closeDialogById('timeout-dialog'); } if (idleModel.expired) { try { this.broadcastService.publish({ type: 'mnsso-logout', payload: 'true', }); } catch { console.log('广播错误'); } this._authService.logout(); } });
这种方式符合单一职责原则,代码逻辑更清晰,也不会出现重复执行startWatching的问题。
方案2:在单个Effect中跟踪Signal变化
如果一定要在同一个Effect中处理,可以通过保存Signal的前值来判断是否是isLoggedIn变化触发了Effect:
private previousLoggedIn = this.isLoggedIn(); effect(() => { const currentLoggedIn = this.isLoggedIn(); const idleModel = this.idleTimeoutSignalModel(); if (environment.idleTimeout) { // 判断是否是isLoggedIn的变化触发了当前Effect if (currentLoggedIn !== this.previousLoggedIn) { this.previousLoggedIn = currentLoggedIn; if (currentLoggedIn) { this.idleTimeoutService.startWatching(); } else { this.idleTimeoutService.stopTimer(); } } // 处理超时状态逻辑 if (currentLoggedIn) { if (idleModel.expiring) { this.dialogService.openTimeOutDialog('延长会话?', '由于长时间未操作,您的登录会话即将过期。是否继续?'); } else { this.dialogService.closeDialogById('timeout-dialog'); } if (idleModel.expired) { try { this.broadcastService.publish({ type: 'mnsso-logout', payload: 'true', }); } catch { console.log('广播错误'); } this._authService.logout(); } } } });
这种方式不需要修改服务端的isTimerRunning逻辑,但代码复杂度比拆分Effect高,仅适合特殊场景下使用。
内容的提问来源于stack exchange,提问作者steveareeno
相关产品推荐
相关产品推荐

