Angular抽象列表组件开发:实现类似MatTable的自定义列表复用
这想法太赞了!打造一个通用的抽象列表组件,把重复的列表逻辑抽离出来,确实能大幅减少冗余代码。下面我一步步教你实现这个类似MatTable风格的AbstractListComponent:
第一步:创建自定义的cellDef结构指令
我们需要一个指令来捕获父组件传递的自定义单元格模板,就像MatTable的*matCellDef一样:
// cell-def.directive.ts import { Directive, TemplateRef } from '@angular/core'; @Directive({ selector: '[cellDef]' }) export class CellDefDirective<T> { constructor(public templateRef: TemplateRef<{ $implicit: T }>) {} }
这个指令的作用是标记父组件提供的模板,并通过$implicit把循环中的当前元素传递给模板。
第二步:实现AbstractListComponent核心逻辑
接下来编写抽象组件的TS代码,用泛型支持任意类型的数据,处理输入输出、模板捕获和事件传递:
// abstract-list.component.ts import { Component, Input, Output, EventEmitter, ContentChild } from '@angular/core'; import { CellDefDirective } from './cell-def.directive'; @Component({ selector: 'abstract-list', templateUrl: './abstract-list.component.html', styleUrls: ['./abstract-list.component.scss'] }) export class AbstractListComponent<T> { // 数据源:支持任意类型的数组 @Input() public dataSource: T[] = []; // 是否显示顶部标签文本 @Input() public displayLabel = true; // 是否启用链接跳转 @Input() public linkActivated = true; // 路由生成函数:父组件自定义每个元素的路由路径,比固定字符串更灵活 @Input() public getRouterLink?: (item: T) => string[] | string; // 元素点击事件输出 @Output() public elementClicked = new EventEmitter<T>(); // 获取父组件传递的cellDef模板 @ContentChild(CellDefDirective) cellDef!: CellDefDirective<T>; constructor() {} // 触发元素点击事件 public onElementClick(item: T): void { this.elementClicked.emit(item); } // 生成当前元素的路由链接(如果启用链接且有路由函数) public getRouterLinkForItem(item: T): string[] | string | null { if (!this.getRouterLink || !this.linkActivated) { return null; } return this.getRouterLink(item); } }
第三步:编写抽象组件的模板
把通用的列表结构、链接逻辑、标签显示逻辑放在这里,用ngTemplateOutlet渲染父组件传递的自定义内容:
<!-- abstract-list.component.html --> <span *ngIf="displayLabel"> {{ "tag.tags" | translate }} {{ "punctuation.doublePoints" | translate }} </span> <!-- 要是想让标签文本更通用,可以加个@Input() labelKey: string; 改成{{ labelKey | translate }} --> <ul class="list-no-style"> <li *ngFor="let item of dataSource" (click)="onElementClick(item)"> <a *ngIf="getRouterLinkForItem(item); else noLink" class="list-element link-no-style" [routerLink]="getRouterLinkForItem(item)" > <!-- 渲染父组件自定义的单元格内容 --> <ng-container [ngTemplateOutlet]="cellDef.templateRef" [ngTemplateOutletContext]="{ $implicit: item }" ></ng-container> </a> <ng-template #noLink> <span class="list-element"> <ng-container [ngTemplateOutlet]="cellDef.templateRef" [ngTemplateOutletContext]="{ $implicit: item }" ></ng-container> </span> </ng-template> </li> </ul>
第四步:改造原有的TagListComponent
现在原来的TagListComponent可以简化成一个轻量的包装组件,只需要处理特定的输入输出和路由逻辑:
// tag-list.component.ts import { Component, Input, Output, EventEmitter } from '@angular/core'; import { Tag } from 'src/app/models'; @Component({ selector: 'app-tag-list', templateUrl: './tag-list.component.html', styleUrls: ['./tag-list.component.scss'], }) export class TagListComponent { @Input() public tagList: Tag[] = []; @Input() public displayLabel = true; @Input() public linkActivated = true; @Output() public tagClicked = new EventEmitter<Tag>(); constructor() {} public onTagClick(tag: Tag): void { this.tagClicked.emit(tag); } // 自定义Tag的路由生成逻辑 public getTagRouterLink(tag: Tag): string[] { return ['/tag/' + tag.urlName]; } }
对应的模板就可以改成你期望的简洁形式:
<!-- tag-list.component.html --> <abstract-list [dataSource]="tagList" [getRouterLink]="getTagRouterLink" [linkActivated]="linkActivated" [displayLabel]="displayLabel" (elementClicked)="onTagClick($event)" > <ng-container *cellDef="let tag"> {{ tag.label }} </ng-container> </abstract-list>
扩展说明
- 泛型支持:因为用了
<T>泛型,这个抽象组件可以直接复用在Artist、Category等其他类型的列表上,只需要传递对应的数据和自定义模板即可。 - 样式复用:把
.list-no-style、.list-element这些通用样式放在abstract-list.component.scss里,避免每个列表组件重复写样式。 - 灵活扩展:如果需要更多自定义选项(比如是否显示序号、是否添加额外操作按钮),可以给抽象组件添加更多
@Input()参数,或者通过额外的ng-content插槽实现。
内容的提问来源于stack exchange,提问作者WabbyWab
相关产品推荐
相关产品推荐

