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

Angular中将领域特定服务接入公共服务的推荐实现方式是什么

推荐实现方案:通用接口适配 + 抽象基类继承

这个方案同时解决你提到的类型不优雅、重复代码多的问题,完全不需要修改swagger生成的API代码,也不用写any类型。


第一步:定义通用适配接口

TypeScript是结构化类型系统,只要两个类型的结构匹配就可以互相兼容,不需要显式实现接口。你可以先定义一套通用的分页结构和服务接口,所有swagger生成的带fetchPage方法的服务会自动适配这个接口:

// 通用分页响应结构
export interface PageResponse<T> {
  content: T[];
  numberOfElements: number;
  totalElements: number;
}

// 通用分页服务接口
export interface PaginationService<T> {
  fetchPage(page: number, pageSize: number, filter?: any): Observable<PageResponse<T>>;
}

第二步:封装抽象基类组件

把所有重复的状态和逻辑都放到抽象基类里,把各组件不同的domainSpecificService和modal定义为抽象属性,要求子类必须实现:

import { NgbModal, NgbModalRef } from '@ng-bootstrap/ng-bootstrap';
import { Observable } from 'rxjs';
import { FormMode } from './你的FormMode定义路径';

// 通用列表基类
export abstract class BaseListComponent<T> {
  // 公共状态
  pageNumber = 0;
  pageSize = 20;
  pageLimit = 10;
  tableMode = FormMode.READ;
  entities: T[] = [];
  numberOfElements = 0;
  totalElements = 0;
  filter?: any;

  // 抽象属性,子类必须实现,传入自己对应的服务和模态框组件
  protected abstract domainService: PaginationService<T>;
  protected abstract modalComponent: any;

  constructor(protected modalService: NgbModal) {}

  // 公共滚动加载逻辑
  onScroll(): void {
    this.pageNumber++;
    if (this.pageNumber <= this.pageLimit) {
      const request$ = this.tableMode === FormMode.READ 
        ? this.domainService.fetchPage(this.pageNumber, this.pageSize)
        : this.domainService.fetchPage(this.pageNumber, this.pageSize, this.filter!);
      
      request$.subscribe(responses => {
        this.entities.push(...responses.content);
        this.numberOfElements += responses.numberOfElements;
        this.totalElements = responses.totalElements;
      });
    }
  }

  // 公共打开模态框逻辑
  openModal(formMode: FormMode, entity?: T): void {
    const modalRef = this.modalService.open(this.modalComponent, { size: 'xl', scrollable: true });
    modalRef.componentInstance.formMode = formMode;
    modalRef.componentInstance.entity = entity;
    this.returnModalResult(modalRef);
  }

  // 公共处理模态框返回逻辑
  returnModalResult(modalRef: NgbModalRef): void {
    modalRef.componentInstance.returnedEntity.subscribe((response: { entity: T, formMode: FormMode }) => {
      switch(response.formMode) {
        case FormMode.CREATE:
          this.reloadPage();
          break;
        case FormMode.UPDATE:
          const itemIndex = this.entities.findIndex(item => (item as any).id == response.entity.id);
          if (itemIndex > -1) this.entities[itemIndex] = response.entity;
          break;
        case FormMode.FILTER:
          this.filter = response.entity;
          this.tableMode = FormMode.FILTER;
          this.reloadPage();
          break;
      }
    })
  }

  // 抽离公共重新加载第一页逻辑,减少重复代码
  private reloadPage(): void {
    this.pageNumber = 0;
    const request$ = this.tableMode === FormMode.READ 
      ? this.domainService.fetchPage(0, this.pageSize)
      : this.domainService.fetchPage(0, this.pageSize, this.filter!);
    
    request$.subscribe(responses => {
      this.entities = [...responses.content];
      this.numberOfElements = responses.numberOfElements;
      this.totalElements = responses.totalElements;
    });
  }
}

第三步:业务组件继承基类

业务组件只需要声明自己对应的服务和模态框组件即可,不需要再写重复逻辑:

@Component({
  selector: 'app-your-business-list',
  templateUrl: './your-business-list.component.html'
})
export class YourBusinessListComponent extends BaseListComponent<你的实体类型> {
  // 实现基类要求的抽象属性
  protected override domainService: PaginationService<你的实体类型> = this.yourGeneratedService;
  protected override modalComponent = 你的业务模态框组件;

  constructor(
    protected override modalService: NgbModal,
    private yourGeneratedService: 你swagger生成的对应服务类型 // 不需要修改原代码,自动适配PaginationService接口
  ) {
    super(modalService);
  }

  // 组件特殊业务逻辑直接在这里添加即可,公共逻辑不需要重复写
}

方案对比

  • 你提到的方案一(公共服务传参):需要每次调用都传递大量组件状态(分页参数、实体列表、模式等),状态同步容易出问题,代码冗余度还是高。
  • 你提到的方案二(纯组件继承):如果没有接口适配,只能用any类型调用服务方法,丢失类型提示,代码可维护性差。
  • 上述组合方案:既复用了90%以上的公共逻辑,又保留了完整的类型提示,子类只需要写差异化的代码,扩展也很灵活,如果有特殊业务逻辑直接在子类重写基类方法即可。

额外优化建议

  • 可以在基类中添加destroy$主题,配合takeUntil操作符自动取消订阅,避免内存泄漏,所有子类不需要额外处理。
  • 如果有部分组件只需要复用部分逻辑,可以把零散的公共方法抽成独立的工具函数/服务,按需调用即可。

内容的提问来源于stack exchange,提问作者Nesd

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 06:45:02