Angular 6中多个Mat-Select滚动到底部触发事件的优化实现问题
优化实现方案
核心思路
抽离所有下拉的通用逻辑,通过统一配置管理不同下拉的专属状态,同时新增请求锁解决重复触发接口的问题,全程无需引入外部依赖。
具体实现步骤
1. 定义通用配置结构存储各下拉状态
无需为每个下拉单独定义数据源、分页、加载状态变量,统一用映射对象管理:
// 定义下拉统一配置结构 interface SelectConfig { dataList: any[]; // 下拉选项数据源 fetchApi: (pageParam: any) => Observable<any>; // 对应下拉的分页请求方法 loading: boolean; // 防止重复请求的锁 currentOffset: number; // 当前分页偏移量 } // 所有下拉的配置集合,新增下拉仅需新增该对象的配置项 selectConfigMap: Record<string, SelectConfig> = { currency: { dataList: [], fetchApi: (param) => this.appService.getAllCurrencies(param), loading: false, currentOffset: 0 }, language: { dataList: [], fetchApi: (param) => this.appService.getAllLanguages(param), loading: false, currentOffset: 0 }, // 剩余国家、组织等下拉直接在此处新增配置即可,无需编写额外逻辑 }; windowSize = 10; private readonly PIXEL_TOLERANCE = 3.0; // 批量获取所有mat-select实例,无需单独声明@ViewChild @ViewChildren(MatSelect) allSelects: QueryList<MatSelect>;
2. 统一注册所有下拉的滚动监听事件
在ngAfterViewInit中批量为所有下拉绑定展开事件和滚动监听,无需单独为每个下拉写订阅逻辑:
ngAfterViewInit() { this.allSelects.forEach(select => { select.openedChange.subscribe((isOpened: boolean) => { if (isOpened) { const panel = select.panel.nativeElement; const selectName = select.ngControl.name; // 先移除旧的滚动监听,避免重复绑定 panel.removeEventListener('scroll', this.handleScroll.bind(this, selectName)); // 绑定新的滚动监听 panel.addEventListener('scroll', this.handleScroll.bind(this, selectName)); } }) }) }
3. 实现通用滚动判断和加载逻辑
所有下拉共用一套触底判断、数据加载逻辑,同时通过loading锁解决重复请求问题:
// 统一滚动事件处理 private handleScroll(selectName: string, event: Event) { const scrollTarget = event.target as HTMLElement; const currentConfig = this.selectConfigMap[selectName]; // 正在加载中、未触底、配置不存在时直接返回 if (!currentConfig || currentConfig.loading || !this.isScrollToBottom(scrollTarget)) { return; } // 上锁,防止重复触发请求 currentConfig.loading = true; currentConfig.currentOffset += this.windowSize; // 构造分页请求参数 const pageParam = { ...this.standardPageSizeObj, pageSize: this.windowSize, offset: currentConfig.currentOffset }; // 调用对应下拉的请求方法 currentConfig.fetchApi(pageParam).subscribe({ next: (resp) => { currentConfig.dataList.push(...resp.data); // 请求完成解锁 currentConfig.loading = false; }, error: () => { // 请求失败也需要解锁,避免后续无法触发加载 currentConfig.loading = false; } }) } // 通用触底判断方法 private isScrollToBottom(target: HTMLElement): boolean { return Math.abs(target.scrollHeight - target.scrollTop - target.clientHeight) < this.PIXEL_TOLERANCE; }
4. 修改HTML模板绑定
将原有单独的数据源替换为配置对象内的对应数据源即可:
<mat-select [disabled]="isDisabled" [(ngModel)]="model.currency" name="currency" (selectionChange)="OnChange('currency',$event.value)"> <mat-option [value]="getCurrency.currencyCode" *ngFor="let getCurrency of selectConfigMap.currency.dataList | filternew:searchCurrencyvaluedrop"> {{ getCurrency.currencyDescription }} </mat-option> </mat-select> <mat-select [disabled]="isDisabled" [(ngModel)]="model.language" name="language" (selectionChange)="OnChange('language', $event.value)"> <mat-option [value]="getLanguage.languageDescription" *ngFor="let getLanguage of selectConfigMap.language.dataList | filternew:searchLanguagevaluedrop"> {{ getLanguage.languageDescription }} </mat-option> </mat-select>
方案优势
- 完全消除代码冗余,新增下拉仅需加1条配置,无需编写重复逻辑,适配任意数量的下拉字段
- 新增的loading锁直接解决滚动到底部触发两次请求的问题
- 未引入任何外部依赖,符合安全限制要求
内容的提问来源于stack exchange,提问作者MenimE
相关产品推荐
相关产品推荐

