You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.14 08:23:47