You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

@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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:49:50