Angular14+中如何为details标签动态添加open属性?
问题分析
你遇到的错误根源有两点:
e.target并非始终指向绑定点击事件的<li>元素:点击<li>内部的<summary>、<img>等子元素时,e.target会指向这些子节点,而非<li>,导致后续获取子元素的逻辑出错。children是HTMLCollection 集合类型,不是单个 DOM 元素,因此无法直接调用setAttribute方法。
另外,Angular 开发中不推荐直接操作 DOM,更建议用数据驱动的属性绑定控制组件状态,避免手动调用 DOM API 引发的问题。
解决方案一:Angular 属性绑定(推荐)
这种方式完全遵循 Angular 数据驱动理念,无需手动操作 DOM:
修改组件类(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]; } }修改模板(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>
额外优化建议
- 模板中图片的
src建议用[src]绑定而非插值表达式{{ }},比如[src]="item.menu.icon",更符合 Angular 规范且避免潜在 XSS 风险。 - 避免在模板中直接调用方法(如
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
相关产品推荐
相关产品推荐

