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

Angular虚拟键盘导航返回时出现object unsubscribed错误求助

问题原因

你遇到的ERROR Error: object unsubscribed错误,本质是内存泄漏+未及时清理订阅导致的:

  • 从第一个组件导航到第二个组件时,第一个组件会被销毁,但如果输入框未触发blur事件(比如快速导航),指令的unsubscribeFromKeyboardEvents方法不会执行,旧的订阅仍然绑定在已销毁的指令实例上。
  • 返回第一个组件时,新的指令实例创建并订阅键盘事件,此时服务发送事件时,旧的已销毁实例的订阅会被触发,导致报错。
  • 当前代码在调用unsubscribe()时未检查订阅对象是否存在,极端情况下可能引发空指针问题。
修复方案

1. 实现ngOnDestroy生命周期钩子

在指令中添加OnDestroy接口,确保组件销毁时强制清理所有订阅,同时清理创建的DOM元素。

修改后的完整指令代码

import {
  Directive,
  OnInit,
  OnDestroy,
  ElementRef,
  HostListener
} from "@angular/core";
import { KeyboardService } from "./keyboard.service";
import { Subscription } from "rxjs";

@Directive({
  selector: "[appOskInput]"
})
export class OskInputDirective implements OnInit, OnDestroy {
  private keySubscription: Subscription;
  private backspaceSubscription: Subscription;
  private enterSubscription: Subscription;
  private measure: HTMLElement;

  constructor(private el: ElementRef, private keyboard: KeyboardService) {}

  ngOnInit() {
    let thisStyle = window.getComputedStyle(this.el.nativeElement);
    this.measure = document.createElement("span");
    this.measure.style.position = "absolute";
    this.measure.style.right = "100%";
    this.measure.style.font = thisStyle.font;
    document.body.appendChild(this.measure);
  }

  ngOnDestroy() {
    this.unsubscribeFromKeyboardEvents();
    // 清理手动创建的DOM元素,避免内存泄漏
    if (this.measure?.parentNode) {
      this.measure.parentNode.removeChild(this.measure);
    }
  }

  @HostListener("focus")
  private onFocus() {
    this.keyboard.fireKeyboardRequested(true);
    this.subscribeToKeyboardEvents();
  }

  @HostListener("blur")
  private onBlur() {
    this.keyboard.fireKeyboardRequested(false);
    this.unsubscribeFromKeyboardEvents();
  }

  private subscribeToKeyboardEvents() {
    // 订阅前先清理旧订阅,防止重复订阅
    this.unsubscribeFromKeyboardEvents();
    this.keySubscription = this.keyboard.keyPressed.subscribe(key =>
      this.onKey(key)
    );
    this.backspaceSubscription = this.keyboard.backspacePressed.subscribe(_ =>
      this.onBackspace()
    );
    this.enterSubscription = this.keyboard.enterPressed.subscribe(_ =>
      this.onEnter()
    );
  }

  private unsubscribeFromKeyboardEvents() {
    // 用可选链避免空指针错误
    this.keySubscription?.unsubscribe();
    this.backspaceSubscription?.unsubscribe();
    this.enterSubscription?.unsubscribe();
    // 重置订阅引用,避免重复取消
    this.keySubscription = null;
    this.backspaceSubscription = null;
    this.enterSubscription = null;
  }

  // 补充原代码缺失的核心功能方法
  private onKey(key: string) {
    this.el.nativeElement.value += key;
    this.updateMeasure();
  }

  private onBackspace() {
    this.el.nativeElement.value = this.el.nativeElement.value.slice(0, -1);
    this.updateMeasure();
  }

  private onEnter() {
    this.el.nativeElement.blur();
  }

  private updateMeasure() {
    this.measure.textContent = this.el.nativeElement.value;
    this.el.nativeElement.style.width = this.measure.offsetWidth + "px";
  }
}

2. 额外优化说明

  • 在subscribeToKeyboardEvents开头先清理旧订阅,避免同一输入框多次触发focus导致重复订阅。
  • 使用可选链操作符?.简化空值检查,让代码更简洁安全。

这样修改后,无论组件是通过blur事件还是销毁触发,都能确保订阅被正确清理,彻底解决object unsubscribed错误,同时避免内存泄漏。

内容的提问来源于stack exchange,提问作者RRB

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 16:36:22