@ng-idle/core双实例异常:自动登出超时致屏保中断失效
Angular 13中@ng-idle/core多实例冲突导致屏保无法关闭的问题
我在Angular 13项目中使用版本为~11.1.0的@ng-idle/core,创建了两个Idle实例,分别用于自动登出和屏保功能。当屏保处于激活状态时,若自动登出实例触发超时,此时操作屏幕无法关闭屏保,UI被阻塞,经确认该场景下屏保的中断订阅已失效。我分析了@ng-idle/core的源码,发现屏保实例设置了timeout: false,理论上不应取消中断订阅,因此怀疑是实例创建方式导致问题,请问该如何解决?
参考示例代码:
import {Component, NgZone, OnInit} from '@angular/core'; import {DEFAULT_INTERRUPTSOURCES, Idle, LocalStorage, LocalStorageExpiry} from '@ng-idle/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit{ title = 'my-idle-app'; private readonly screenSaver!: HTMLElement; private readonly screenSaverText!: Text; idleTime1: number = 1; private screenSaverIdle = new Idle(new LocalStorageExpiry(new LocalStorage()), this.ngZone); constructor(readonly ngZone: NgZone, private idle1: Idle) { this.screenSaver = document.createElement('div'); this.screenSaver.id = 'screensaver'; this.screenSaver.className = 'screen-saver'; this.screenSaverText = document.createTextNode('---%'); const movingTxt = document.createElement('span'); movingTxt.appendChild(this.screenSaverText); this.screenSaver.appendChild(movingTxt); this.removeScreenSaver(); this.screenSaverIdle.setIdleName('screenSaverIdleService'); this.screenSaverIdle.setInterrupts(DEFAULT_INTERRUPTSOURCES); this.screenSaverIdle.setTimeout(false); this.screenSaverIdle.setIdle(10); this.screenSaverIdle.watch(); this.screenSaverIdle.onIdleStart.subscribe(() => { document.body.appendChild(this.screenSaver); }); this.screenSaverIdle.onIdleEnd.subscribe(() => { console.log('IDLE END ::'); this.removeScreenSaver(); }); } ngOnInit() { this.idle1.setIdleName('AutoLogoutIdleService'); this.idle1.setInterrupts(DEFAULT_INTERRUPTSOURCES); this.idle1.setIdle(this.idleTime1); this.idle1.setTimeout(10); this.idle1.onIdleStart.subscribe(() => { console.log("IDLE 1 STARTS") }); this.idle1.onIdleEnd.subscribe({ next: () => { console.log("IDLE 1 END"); }, complete: () => { console.log("idle 1 completed"); } }); this.idle1.onTimeout.subscribe(()=> { console.log("User session timed Out",); }); this.idle1.watch(); } private removeScreenSaver() { const ex = document.getElementById('screensaver'); if (ex) { document.body.removeChild(ex); } } }
解决方案
1. 通过Angular依赖注入管理所有Idle实例
你当前手动new Idle()创建屏保实例的方式,脱离了Angular的依赖注入容器,容易导致实例间的中断源订阅冲突。正确的做法是为屏保实例单独创建一个DI提供者,确保两个实例完全独立:
首先修改组件的providers配置:
import {Inject} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'], providers: [ { provide: 'ScreenSaverIdle', useFactory: (ngZone: NgZone) => { return new Idle(new LocalStorageExpiry(new LocalStorage()), ngZone); }, deps: [NgZone] } ] })
然后在构造函数中注入这个实例,替代手动创建的方式:
constructor( readonly ngZone: NgZone, private idle1: Idle, @Inject('ScreenSaverIdle') private screenSaverIdle: Idle ) { // 后续逻辑保持不变 }
2. 自动登出超时后重置屏保实例状态
当自动登出实例触发超时后,主动重置屏保Idle的监听状态,确保其中断订阅不会失效:
在自动登出的onTimeout回调中添加重置逻辑:
this.idle1.onTimeout.subscribe(()=> { console.log("User session timed Out"); // 停止并重新启动屏保实例的监听 this.screenSaverIdle.stop(); this.screenSaverIdle.watch(); });
3. 为两个实例分配独立的中断源
避免共享DEFAULT_INTERRUPTSOURCES导致的冲突,可为屏保实例自定义中断源:
// 复制默认中断源并做自定义调整,确保和自动登出实例的中断源不冲突 const SCREENSAVER_INTERRUPTS = [...DEFAULT_INTERRUPTSOURCES]; // 可根据需求移除或添加特定中断源 this.screenSaverIdle.setInterrupts(SCREENSAVER_INTERRUPTS);
内容的提问来源于stack exchange,提问作者Vinay
相关产品推荐
相关产品推荐

