Angular中ngFor结合mat-accordion实现多分类搜索功能时遍历异常问题求助
问题分析与解决方案
这个问题我之前也碰到过,核心原因是你循环生成的多个过滤器面板用了重复的id属性(searchInput和searchUL)。在HTML规范里id是全局唯一标识,document.getElementById()只会返回页面中第一个匹配该id的元素,所以不管你在哪个面板的搜索框输入内容,实际操作的都是第一个面板的列表,自然其他面板的搜索功能失效了。
下面给你两种解决方案,优先推荐Angular数据驱动的实现方式,更符合框架设计理念:
方案一:使用Angular数据绑定+过滤逻辑(推荐)
完全避免直接操作DOM,通过绑定搜索词到每个分类项,再用数据过滤实现搜索功能,更稳定也更易维护。
1. 修改HTML模板
<div *ngFor="let item of categoryFilters?.filter; let i = index"> <div class="FilterDirectory-titleBar" > <!-- 绑定当前分类项的搜索词,去掉重复id --> <input type="text" [(ngModel)]="item.searchTerm" class="FilterDirectory-searchInput" placeholder="搜索..." /> <span class="close-search" (click)="item.showSearch = false" > <i class="fa fa-times" aria-hidden="true"></i > </span> </div> <div class="card product-list-header" style="border: none" > <span class="open-search" (click)="item.showSearch = true"> <i class="fa fa-search"></i> </span> <mat-accordion class="filter-product-expansion"> <mat-expansion-panel [expanded]="true"> <mat-expansion-panel-header style="background-color: #fff" > <h5 class="float-left" > {{ item.title }} </h5> </mat-expansion-panel-header> <span class="clearfix"></span> <ul> <!-- 调用过滤方法,仅显示匹配搜索词的项 --> <div *ngFor="let items of getFilteredItems(item); let i = index"> <li *ngIf="items.active"> <a class="search-title">{{ items.title }}</a> <div class="card-body" > <div class="form-group"> <div class="row"> <div class="col-md-10 filter-col" style="padding: 0" > <label class="radio-inline"> <p-checkbox name="item.title" value="{{ items?.uuid }}" label="{{ items?.title }}" > </p-checkbox> </label> </div> <div class="product-count"> <p> ({{ items.productCount ? items.productCount : "" }}) </p> </div> </div> </div> </div> </li> </div> </ul> </mat-expansion-panel> </mat-accordion> </div> </div>
2. 修改TS代码
首先初始化每个分类项的搜索词,再添加过滤方法:
// 在获取categoryFilters数据后(比如ngOnInit或数据订阅回调中),初始化搜索词 ngOnInit() { // 假设你已经获取到categoryFilters数据 this.categoryFilters.filter.forEach(item => { item.searchTerm = ''; // 初始化搜索词为空 }); } // 根据当前分类项的搜索词过滤列表 getFilteredItems(item: any): any[] { // 搜索词为空时返回全部数据 if (!item.searchTerm.trim()) { return item.value; } const filterTerm = item.searchTerm.toUpperCase(); // 过滤出标题包含搜索词的项 return item.value.filter(items => items.title.toUpperCase().includes(filterTerm) ); }
方案二:修改DOM元素ID为唯一值(兼容原有逻辑)
如果你想保留原有DOM操作的逻辑,只需要给每个面板的搜索框和列表设置唯一ID,再传递索引区分:
1. 修改HTML模板
<div *ngFor="let item of categoryFilters?.filter; let i = index"> <div class="FilterDirectory-titleBar" > <!-- 设置唯一ID:searchInput-索引值 --> <input type="text" id="searchInput-{{i}}" (input)="searchCategory(i)" class="FilterDirectory-searchInput" /> <span class="close-search" (click)="item.showSearch = false" > <i class="fa fa-times" aria-hidden="true"></i > </span> </div> <div class="card product-list-header" style="border: none" > <span class="open-search" (click)="item.showSearch = true"> <i class="fa fa-search"></i> </span> <mat-accordion class="filter-product-expansion"> <mat-expansion-panel [expanded]="true"> <mat-expansion-panel-header style="background-color: #fff" > <h5 class="float-left" > {{ item.title }} </h5> </mat-expansion-panel-header> <span class="clearfix"></span> <!-- 设置唯一ID:searchUL-索引值 --> <ul id="searchUL-{{i}}"> <div *ngFor="let items of item.value; let i = index"> <li *ngIf="items.active"> <a class="search-title">{{ items.title }}</a> <div class="card-body" > <div class="form-group"> <div class="row"> <div class="col-md-10 filter-col" style="padding: 0" > <label class="radio-inline"> <p-checkbox name="item.title" value="{{ items?.uuid }}" label="{{ items?.title }}" > </p-checkbox> </label> </div> <div class="product-count"> <p> ({{ items.productCount ? items.productCount : "" }}) </p> </div> </div> </div> </div> </li> </div> </ul> </mat-expansion-panel> </mat-accordion> </div> </div>
2. 修改TS的searchCategory方法
searchCategory(index: number) { var input, filter, ul, li, a, i, txtValue; // 根据索引获取当前面板的搜索框和列表 input = document.getElementById(`searchInput-${index}`); filter = input.value.toUpperCase(); ul = document.getElementById(`searchUL-${index}`); li = ul.getElementsByTagName('li'); for (i = 0; i < li.length; i++) { a = li[i].getElementsByTagName('a')[0]; txtValue = a.textContent || a.innerText; if (txtValue.toUpperCase().indexOf(filter) > -1) { li[i].style.display = ''; } else { li[i].style.display = 'none'; } } }
内容的提问来源于stack exchange,提问作者Bhavya
相关产品推荐
相关产品推荐

