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

Angular14+中如何为details标签动态添加open属性?

问题分析

你遇到的错误根源有两点:

  1. e.target 并非始终指向绑定点击事件的 <li> 元素:点击 <li> 内部的 <summary>、<img> 等子元素时,e.target 会指向这些子节点,而非 <li>,导致后续获取子元素的逻辑出错。
  2. children 是HTMLCollection 集合类型,不是单个 DOM 元素,因此无法直接调用 setAttribute 方法。

另外,Angular 开发中不推荐直接操作 DOM,更建议用数据驱动的属性绑定控制组件状态,避免手动调用 DOM API 引发的问题。


解决方案一:Angular 属性绑定(推荐)

这种方式完全遵循 Angular 数据驱动理念,无需手动操作 DOM:

  1. 修改组件类(side-nav.ts)
    给每个菜单项维护独立的展开状态,避免所有子菜单共用一个状态变量:

    export class SideMenuComponent {
      @Input() sideNavStatus: boolean = false;
      // 用对象存储每个菜单的展开状态,以菜单名称作为唯一标识
      subMenuStates: { [key: string]: boolean } = {};
    
      list: MenuList[] = [...];
    
      changeSideNavButton(): string {
        return this.sideNavStatus 
          ? '../../../assets/side-menu-icons/menu-collapsed-svgrepo-com.svg'
          : '../../../assets/side-menu-icons/menu-expand-svgrepo-com.svg';
      }
    
      // 切换指定菜单的展开状态
      toggleSubMenu(menuName: string) {
        this.subMenuStates[menuName] = !this.subMenuStates[menuName];
      }
    }
    
  2. 修改模板(side-nav.html)
    给 <details> 绑定 [open] 属性,点击 <li> 时触发状态切换,同时同步更新箭头样式:

    <div class="side-nav-content">
      <ul class="nav-list" [ngClass]="{ 'nav-list-open': sideNavStatus }">
        <a class="nav-list-item" (click)="sideNavStatus = !sideNavStatus">
          <img [src]="changeSideNavButton()" alt="Menu burguer icon" />
        </a>
        <li
          class="nav-list-item"
          *ngFor="let item of list"
          (click)="toggleSubMenu(item.menu.name)"
        >
          <details [open]="subMenuStates[item.menu.name]">
            <summary>
              <img
                [src]="item.menu.icon"
                alt="{{ item.menu.name }} menu icon"
                class="item-icon"
              />
              <span class="item-title" [ngClass]="{ 'show-title': sideNavStatus }">
                <a class="item-title-text">
                  {{ item.menu.name }}
                  <img
                    src="../../../assets/side-menu-icons/arrow-down-svgrepo-com.svg"
                    alt="Arrow down Icon"
                    class="item-arrow-icon"
                    [ngClass]="{ 'sub-menu-open': subMenuStates[item.menu.name] }"
                  />
                </a>
              </span>
            </summary>
            <div class="sub-menu-content">
              <ul class="sub-list">
                <li
                  class="sub-list-item"
                  *ngFor="let subItem of item.menu.subMenuList"
                >
                  <img [src]="subItem.icon" alt="{{ subItem.name }} menu icon" />
                  <span>
                    <a href="">
                      {{ subItem.name }}
                      <img
                        [ngClass]="{ 'show-arrow': subItem.subSubMenuList }"
                        src="../../../assets/side-menu-icons/arrow-down-svgrepo-com.svg"
                        alt="Arrow down Icon"
                      />
                    </a>
                  </span>
                </li>
              </ul>
            </div>
          </details>
        </li>
      </ul>
    </div>
    

解决方案二:直接操作 DOM(适合快速调试,不推荐长期使用)

如果一定要手动操作 DOM,需确保准确获取目标 <details> 元素:

修改 onSubMenu 方法,使用 e.currentTarget(始终指向绑定点击事件的 <li>)定位内部的 <details>:

onSubMenu(e: MouseEvent) {
  // currentTarget 是绑定点击事件的 li 元素
  const liEl = e.currentTarget as HTMLLIElement;
  const detailsEl = liEl.querySelector('details') as HTMLDetailsElement;
  if (detailsEl) {
    // 切换展开状态
    detailsEl.open = !detailsEl.open;
    // 同步更新箭头样式的状态变量(注意:多菜单需单独维护状态,否则所有箭头会同步变化)
    this.subMenuStatus = detailsEl.open;
  }
}

同时给 <summary> 添加阻止事件冒泡的逻辑,避免点击摘要时触发两次切换:

<summary (click)="$event.stopPropagation()">
  <!-- 原有摘要内容 -->
</summary>

额外优化建议

  1. 模板中图片的 src 建议用 [src] 绑定而非插值表达式 {{ }},比如 [src]="item.menu.icon",更符合 Angular 规范且避免潜在 XSS 风险。
  2. 避免在模板中直接调用方法(如 changeSideNavButton()),每次变更检测都会执行该方法影响性能,可改为在 sideNavStatus 变化时更新图片路径:
    menuButtonSrc = '../../../assets/side-menu-icons/menu-expand-svgrepo-com.svg';
    private _sideNavStatus = false;
    
    @Input() set sideNavStatus(status: boolean) {
      this._sideNavStatus = status;
      this.menuButtonSrc = status 
        ? '../../../assets/side-menu-icons/menu-collapsed-svgrepo-com.svg'
        : '../../../assets/side-menu-icons/menu-expand-svgrepo-com.svg';
    }
    get sideNavStatus() {
      return this._sideNavStatus;
    }
    
    模板中用 [src]="menuButtonSrc" 替代原有的插值逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 04:12:49