给装饰器类注入额外服务是否为反模式?Angular标题服务实现疑问
问题
我想在整个Angular应用中自定义Title类,为setTitle()添加扩展功能:部分组件直接调用@angular/platform的Title类setTitle方法,部分组件传入翻译键(如MY_USERS_PAGE_TITLE),获取翻译结果后设置标题。尝试继承Title类失败,因为它被标注为final类。于是我创建了可注入的装饰器类:
@Injectable({ providedIn: 'root' }) class TitleDecorator implements ITitle{ protected title!: string; constructor( protected titleService: Title ) { } setTitle(newTitle: string): void { this.titleService.setTitle(newTitle); } getTitle(): string { return this.titleService.getTitle(); } addTrailing(trailing: string): void { this.title += trailing; this.setTitle(this.title); } }
并继承该类实现了支持翻译的TitleServiceTranslatable:
@Injectable({ providedIn: 'root' }) export class TitleServiceTranslatable extends TitleDecorator{ constructor( private translateService: TranslateService, protected titleService: Title ) { super(titleService); } private subscriptions = new Subscription(); setTitle(newTitle: string): void { this.subscriptions.add( this.translateService.get(newTitle).subscribe(title => { this.title = title; super.setTitle(this.title); this.subscriptions.unsubscribe(); }) ); } }
目前功能正常,但我有疑问:通过Angular DI自动注入额外服务后,装饰器理论上无法互相替换,这种实现方式是否属于反模式?恳请提供优化建议及更好的解决方案。
分析与优化建议
是否属于反模式
你的实现不算严格意义上的反模式,但存在依赖耦合过紧和扩展灵活性不足的问题:
TitleServiceTranslatable直接绑定TranslateService,导致它无法脱离翻译场景复用,也难以替换为其他扩展逻辑的装饰器TitleDecorator未抽象出通用扩展点,后续新增功能(如标题前缀、动态拼接)只能通过继承叠加,容易形成臃肿的类层次结构
优化方案
方案1:DI替换原生Title服务(推荐)
既然原生Title是final类无法继承,直接在Angular DI容器中替换它的实现,让所有组件注入的Title都是自定义版本,同时兼容两种调用方式:
import { Inject, Injectable } from '@angular/core'; import { Title, DOCUMENT } from '@angular/platform-browser'; import { TranslateService } from '@ngx-translate/core'; import { catchError, of } from 'rxjs'; @Injectable({ providedIn: 'root' }) export class CustomTitleService extends Title { private currentTitle = ''; constructor( @Inject(DOCUMENT) document: any, // 原生Title依赖的DOCUMENT private translateService: TranslateService ) { super(document); } setTitle(newTitle: string): void { // 优先尝试翻译,翻译失败或无匹配键时直接使用原字符串 this.translateService.get(newTitle).pipe( catchError(() => of(newTitle)) ).subscribe(translatedTitle => { this.currentTitle = translatedTitle; super.setTitle(this.currentTitle); }); } addTrailing(trailing: string): void { this.currentTitle += trailing; super.setTitle(this.currentTitle); } }
在AppModule的providers中替换原生服务:
import { Title } from '@angular/platform-browser'; import { CustomTitleService } from './custom-title.service'; @NgModule({ providers: [ { provide: Title, useClass: CustomTitleService } ] }) export class AppModule { }
这样所有组件注入Title时,拿到的都是CustomTitleService,无需区分调用不同服务,直接传入字符串或翻译键即可。
方案2:策略模式分离扩展逻辑
如果需要支持多场景动态切换标题处理逻辑(如翻译、前缀拼接、格式化),可以用策略模式拆分功能:
- 定义策略接口:
import { Observable } from 'rxjs'; export interface TitleProcessingStrategy { process(title: string): Observable<string>; }
- 实现翻译策略:
import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { TitleProcessingStrategy } from './title-processing-strategy'; import { catchError, of } from 'rxjs'; @Injectable() export class TranslationStrategy implements TitleProcessingStrategy { constructor(private translateService: TranslateService) {} process(title: string): Observable<string> { return this.translateService.get(title).pipe( catchError(() => of(title)) ); } }
- 实现基础策略(直接返回原标题):
import { Injectable } from '@angular/core'; import { TitleProcessingStrategy } from './title-processing-strategy'; import { of } from 'rxjs'; @Injectable() export class BasicStrategy implements TitleProcessingStrategy { process(title: string) { return of(title); } }
- 实现策略驱动的自定义Title服务:
import { Inject, Injectable } from '@angular/core'; import { Title, DOCUMENT } from '@angular/platform-browser'; import { TitleProcessingStrategy } from './title-processing-strategy'; import { BasicStrategy } from './basic.strategy'; @Injectable({ providedIn: 'root' }) export class StrategyTitleService extends Title { private currentStrategy: TitleProcessingStrategy; private currentTitle = ''; constructor( @Inject(DOCUMENT) document: any, private basicStrategy: BasicStrategy, private translationStrategy: TranslationStrategy ) { super(document); this.currentStrategy = this.basicStrategy; // 默认使用基础策略 } // 动态切换策略 useStrategy(strategy: TitleProcessingStrategy): void { this.currentStrategy = strategy; } setTitle(newTitle: string): void { this.currentStrategy.process(newTitle).subscribe(processedTitle => { this.currentTitle = processedTitle; super.setTitle(this.currentTitle); }); } addTrailing(trailing: string): void { this.currentTitle += trailing; super.setTitle(this.currentTitle); } }
这种方式的优势是:新增功能只需添加策略类,无需修改原有服务;可根据页面场景动态切换处理逻辑,符合单一职责原则。
现有代码的小修复
如果暂时不想重构,可先修复内存泄漏问题:
// TitleServiceTranslatable中修改订阅管理 private subscriptions = new Subscription(); setTitle(newTitle: string): void { const sub = this.translateService.get(newTitle).subscribe(title => { this.title = title; super.setTitle(this.title); }); this.subscriptions.add(sub); } // 实现OnDestroy接口清理订阅 ngOnDestroy(): void { this.subscriptions.unsubscribe(); }
内容的提问来源于stack exchange,提问作者Fatih Ersoy
相关产品推荐
相关产品推荐

