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
相关产品推荐
相关产品推荐

