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

如何实现用户无操作指定时长后循环弹出模态框,现有代码仅首次生效问题排查

问题根因排查
  • 订阅逻辑失效:组件仅在ngOnInit阶段订阅了第一次生成的observeable$,每次触发弹窗后调用initScreenListen会重新赋值服务里的observeable$,新的 observable 没有被任何订阅者监听,自然不会触发后续逻辑。
  • 事件监听异常:merge里重复注册了2次document的MOUSE_MOVE事件,还有拼写错误BanMouseEvent.CLIK应为BanMouseEvent.CLICK,多次调用initScreenListen会持续注册新的事件监听,导致内存泄漏、逻辑冲突。
  • 无旧订阅清理逻辑:每次重新初始化监听时,没有取消之前的事件订阅、定时器订阅,旧的逻辑还在后台运行,导致计数混乱。
修复方案

第一步:修改服务代码

import { Subscription, Subject, Observable } from 'rxjs';

export class InactivityListenService {
  private destroySub = new Subscription();
  // 用Subject对外统一通知超时事件
  public inactivityTimeout$ = new Subject<void>();
  // 无操作超时时间,单位:秒
  private inactivityTime = 5;

  public initScreenListen(): void {
    // 先清理之前的所有订阅,避免旧逻辑干扰
    this.destroySub.unsubscribe();
    this.destroySub = new Subscription();

    const mergedObservable$ = merge(
      fromEvent(document, BanKeyboardEvent.KEY_DOWN),
      fromEvent(document, BanMouseEvent.MOUSE_DOWN),
      fromEvent(document, BanMouseEvent.MOUSE_MOVE),
      fromEvent(document, BanMouseEvent.WHEEL),
      fromEvent(document, BanMouseEvent.CLICK), // 修复拼写错误
      fromEvent(window, BanMouseEvent.MOUSE_MOVE)
    );
    this.startTimer(mergedObservable$);
  }

  private startTimer(mergedObservable$: Observable<Event>): void {
    this.ngZone.runOutsideAngular(() => {
      const timerSub = mergedObservable$.pipe(
        // 每次检测到用户操作就重置定时器
        switchMap(() => interval(1000).pipe(take(this.inactivityTime))),
        tap((value) => this.isItTimeToShowPopUp(value)),
        skipWhile(x => x !== this.inactivityTime - 1)
      ).subscribe(() => {
        // 超时后对外发送通知
        this.inactivityTimeout$.next();
      });
      this.destroySub.add(timerSub);
    });
  }

  private isItTimeToShowPopUp(val: number): void {
    this.timeLeftForInactive = this.inactivityTime - val;
    if (this.timeLeftForInactive <= 13) {
      this.timeLapsedSinceInactivity = this.timeLeftForInactive;
      this.ref.tick();
      console.log(this.timeLeftForInactive);
    }
  }

  // 组件销毁时调用,全量清理资源
  public destroy(): void {
    this.destroySub.unsubscribe();
    this.inactivityTimeout$.complete();
  }
}

第二步:修改组件代码

import { Subscription } from 'rxjs';

// 组件内声明销毁订阅容器
private componentDestroySub = new Subscription();

public ngOnInit(): void {
  this.inactivityListenService.initScreenListen();
  // 只需要订阅一次超时通知即可
  const timeoutSub = this.inactivityListenService.inactivityTimeout$.subscribe(() => {
    console.log('here show modal.....');
    // 弹窗后重新初始化监听即可,不需要重复订阅
    this.inactivityListenService.initScreenListen();
  });
  this.componentDestroySub.add(timeoutSub);
}

// 组件销毁钩子清理所有订阅,避免内存泄漏
public ngOnDestroy(): void {
  this.componentDestroySub.unsubscribe();
  this.inactivityListenService.destroy();
}
优化说明
  • 不需要对外暴露内部的可观察对象,用统一的通知Subject对外传递事件,避免订阅管理混乱
  • 所有订阅都集中在销毁容器里统一管理,销毁时一次性清理,不会出现残留的后台逻辑
  • 修复了事件拼写错误和重复注册问题,避免多余的事件触发

内容的提问来源于stack exchange,提问作者Julio Rodríguez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 02:57:01