如何在TypeScript组件中实现泛型类以复用列表选择组件?
实现Angular泛型选择列表组件(app-select-item)
可行性结论
完全可行,TypeScript的泛型机制结合Angular的组件输入输出体系,能够完美实现类似C#泛型类的复用效果,完全覆盖你提到的需求场景。
实现步骤与代码示例
1. 定义泛型组件类
创建app-select-item组件,通过<T>声明泛型类型,将输入、输出及API函数都绑定到该类型:
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { debounceTime, distinctUntilChanged, switchMap, filter } from 'rxjs/operators'; import { FormControl } from '@angular/forms'; @Component({ selector: 'app-select-item', templateUrl: './select-item.component.html', styleUrls: ['./select-item.component.css'] }) export class SelectItemComponent<T> implements OnInit { // 初始选中项 @Input() initialSelected: T | null = null; // 获取数据列表的API函数,支持传入搜索关键词,返回Observable<T[]> @Input() apiFunc: (searchTerm?: string) => Observable<T[]>; // 可选:指定列表项显示的字段(比如Customer的name,Contract的contractNumber) @Input() displayField: keyof T = 'name' as keyof T; // 选中项输出事件 @Output() select = new EventEmitter<T>(); // 搜索控件 searchControl = new FormControl(''); // 数据列表(Observable形式,配合async管道渲染) items$: Observable<T[]>; // 当前选中项 selectedItem: T | null = null; ngOnInit(): void { this.selectedItem = this.initialSelected; // 实现带防抖的搜索筛选逻辑 this.items$ = this.searchControl.valueChanges.pipe( debounceTime(300), distinctUntilChanged(), filter(term => term.length >= 0), // 允许空搜索加载全部数据 switchMap(term => this.apiFunc(term)) ); } // 选中项处理逻辑 onSelect(item: T): void { this.selectedItem = item; this.select.emit(item); } }
2. 组件模板(select-item.component.html)
模板中通过displayField动态绑定泛型对象的显示字段,适配不同模型的渲染需求:
<div class="search-bar"> <input type="text" [formControl]="searchControl" placeholder="输入关键词搜索..." /> </div> <ul class="item-list"> <li *ngFor="let item of items$ | async" [class.active]="selectedItem === item" (click)="onSelect(item)" > {{ item[displayField] }} </li> </ul>
3. 父组件中使用泛型组件
在父组件模板中,直接传入对应模型的API函数、初始选中项及显示字段,TypeScript会自动推断泛型类型:
<!-- 客户选择组件 --> <app-select-item [initialSelected]="currentCustomer" [apiFunc]="customerApi.getCustomers" [displayField]="'name'" (select)="handleCustomerSelect($event)" ></app-select-item> <!-- 合同选择组件 --> <app-select-item [initialSelected]="currentContract" [apiFunc]="contractApi.getContracts" [displayField]="'contractNumber'" (select)="handleContractSelect($event)" ></app-select-item>
父组件TypeScript示例:
import { Component } from '@angular/core'; import { Customer } from './models/customer.model'; import { Contract } from './models/contract.model'; import { CustomerApiService } from './services/customer-api.service'; import { ContractApiService } from './services/contract-api.service'; @Component({ selector: 'app-parent', templateUrl: './parent.component.html' }) export class ParentComponent { currentCustomer: Customer | null = null; currentContract: Contract | null = null; constructor( private customerApi: CustomerApiService, private contractApi: ContractApiService ) {} handleCustomerSelect(customer: Customer): void { this.currentCustomer = customer; // 后续业务逻辑处理 } handleContractSelect(contract: Contract): void { this.currentContract = contract; // 后续业务逻辑处理 } }
4. 补充说明
- 如果需要基于模型类做实例化操作(比如创建空对象),可以添加
@Input() modelClass: new () => T;,通过new this.modelClass()生成实例。 - 泛型类型
T支持显式声明(如<app-select-item<Customer>>),但通常父组件传入对应API后TypeScript会自动推断,无需额外配置。
内容的提问来源于stack exchange,提问作者Freek Wiekmeijer
相关产品推荐
相关产品推荐

