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

Angular自定义指令多实例仅单实例生效,上下箭头焦点控制异常

问题根因
  • 所有appFocuser指令实例都监听了全局document的keyup事件,每次按下上下箭头时,所有实例的回调函数都会顺序执行,后执行的实例会覆盖前一个实例的焦点切换操作,因此只有最后注册的第二个实例看起来能正常工作
  • 没有做触发范围校验:不管当前焦点是否在当前指令对应的容器内,所有实例都会执行焦点切换逻辑,导致不同实例的操作互相冲突
修复方案

核心逻辑增加触发范围校验,只有当当前激活的元素属于当前指令管辖的容器内部时,才响应上下箭头按键事件。如果需要优先保障第一个区域默认获焦,可以增加输入参数控制默认获焦行为,避免多个实例初始化时互相抢夺焦点。

修改后完整指令代码如下:

import {
  Directive,
  ElementRef,
  HostListener,
  AfterViewInit,
  Input
} from '@angular/core';

@Directive({
  selector: '[appFocuser]',
})
export class AutoFocusDirective implements AfterViewInit {
  focusableArray = [];
  parent = null;
  count = 0;
  currentFocus: HTMLElement;
  // 增加输入参数,控制是否初始化时自动获焦,默认关闭
  @Input() autoFocusOnInit = false;
  constructor(private el: ElementRef) {}

  @HostListener('document:keyup', ['$event']) onKeyup = ($event: KeyboardEvent) => {
    // 新增范围校验:当前激活元素不在当前容器内时直接返回,不处理事件
    const activeElement = document.activeElement;
    if (!activeElement || !this.parent.contains(activeElement)) {
      return;
    }
    if ($event.code === 'ArrowDown') {
      $event.preventDefault();
      $event.stopPropagation();
      if (this.count >= this.focusableArray.length - 1) {
        this.count = 0;
      } else {
        this.count++;
      }
      this.focusableArray[this.count].focus();
      return;
    }
    if ($event.code === 'ArrowUp') {
      $event.preventDefault();
      $event.stopPropagation();
      if (this.count == 0) {
        this.count = this.focusableArray.length - 1;
      } else {
        this.count--;
      }
      this.focusableArray[this.count].focus();
      return;
    }
  };

  ngAfterViewInit() {
    this.parent = this.el.nativeElement;
    this.focusableArray = Array.from(
      this.parent.querySelectorAll(`[data-focus=true]`)
    ) as HTMLElement[];
    // 只有配置了autoFocusOnInit为true的实例才会在初始化时自动获焦
    if (this.autoFocusOnInit && this.focusableArray.length) {
      this.currentFocus = this.focusableArray[this.count];
      this.currentFocus.focus();
    }
  }
}
使用示例

需要优先响应的第一个绿色区域的容器,绑定指令时开启自动获焦即可:

<!-- 第一个容器,开启默认获焦,优先响应 -->
<div appFocuser [autoFocusOnInit]="true" class="green-area">
  <!-- 内部带data-focus=true的可聚焦元素 -->
</div>

<!-- 第二个容器,不开启默认获焦 -->
<div appFocuser class="other-area">
  <!-- 内部带data-focus=true的可聚焦元素 -->
</div>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.01 00:15:02