如何在其他地方使用 LanguageService 的 language$
概述
LanguageService.language$ 是一個 ReplaySubject<LangChangeEvent>(1),用於發佈語言變化事件。本文說明如何在組件、服務和其他地方正確使用它。
1. 在組件中使用
基本訂閱模式
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { LanguageService } from '../../base/services/language.service';
@Component({
selector: 'app-header',
template: `
<div>當前語言: {{ currentLang }}</div>
<button (click)="changeLanguage('zh-tw')">中文</button>
<button (click)="changeLanguage('en-us')">English</button>
`
})
export class HeaderComponent implements OnInit, OnDestroy {
currentLang = 'en-us';
private subscription?: Subscription;
constructor(private languageService: LanguageService) {}
ngOnInit() {
// ✅ 正確:訂閱語言變化
this.subscription = this.languageService.language$.subscribe(event => {
console.log('語言改變:', event.lang);
this.currentLang = event.lang;
// 可以在這裡執行其他邏輯
this.updateUI(event.lang);
});
}
ngOnDestroy() {
// ✅ 重要:取消訂閱防止記憶體洩漏
this.subscription?.unsubscribe();
}
changeLanguage(lang: string) {
this.languageService.setLang(lang);
}
private updateUI(lang: string) {
// 更新組件狀態或執行其他邏輯
console.log('UI 更新為語言:', lang);
}
}
使用 Async Pipe (推薦)
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { LanguageService } from '../../base/services/language.service';
@Component({
selector: 'app-language-indicator',
template: `
<!-- Async pipe 自動處理訂閱和取消訂閱 -->
<div>當前語言: {{ currentLang$ | async }}</div>
<div>語言事件: {{ languageEvent$ | async | json }}</div>
`
})
export class LanguageIndicatorComponent {
// 映射為語言代碼
currentLang$ = this.languageService.language$.pipe(
map(event => event.lang)
);
// 直接使用完整事件
languageEvent$ = this.languageService.language$;
constructor(private languageService: LanguageService) {}
// 不需要手動取消訂閱,Async pipe 會處理
}
2. 在服務中使用
服務間通信
import { Injectable } from '@angular/core';
import { LanguageService } from './language.service';
@Injectable({
providedIn: 'root'
})
export class TranslationService {
constructor(private languageService: LanguageService) {
// 在服務中訂閱語言變化
this.languageService.language$.subscribe(event => {
console.log('重新載入翻譯文件:', event.lang);
this.loadTranslations(event.lang);
});
}
private loadTranslations(lang: string) {
// 載入對應語言的翻譯文件
}
}
組合多個 Observable
import { Injectable } from '@angular/core';
import { combineLatest } from 'rxjs';
import { map } from 'rxjs/operators';
import { LanguageService } from './language.service';
import { UserService } from './user.service';
@Injectable({
providedIn: 'root'
})
export class LocalizationService {
// 組合語言和用戶偏好設定
localizedSettings$ = combineLatest([
this.languageService.language$,
this.userService.userPreferences$
]).pipe(
map(([langEvent, preferences]) => ({
language: langEvent.lang,
locale: this.getLocale(langEvent.lang, preferences.region)
}))
);
constructor(
private languageService: LanguageService,
private userService: UserService
) {}
private getLocale(lang: string, region: string): string {
// 返回完整的 locale 字符串,如 'zh-TW', 'en-US'
return `${lang}-${region}`;
}
}
3. 在指令中使用
import { Directive, OnInit, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
import { LanguageService } from '../services/language.service';
@Directive({
selector: '[appLanguageAware]'
})
export class LanguageAwareDirective implements OnInit, OnDestroy {
private subscription?: Subscription;
constructor(private languageService: LanguageService) {}
ngOnInit() {
this.subscription = this.languageService.language$.subscribe(event => {
// 根據語言變化更新指令行為
this.updateDirectiveBehavior(event.lang);
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
private updateDirectiveBehavior(lang: string) {
// 更新指令邏輯
}
}
4. 在 Guard 中使用
import { Injectable } from '@angular/core';
import { CanActivate } from '@angular/router';
import { Observable, map } from 'rxjs';
import { LanguageService } from '../services/language.service';
@Injectable({
providedIn: 'root'
})
export class LanguageGuard implements CanActivate {
constructor(private languageService: LanguageService) {}
canActivate(): Observable<boolean> {
// 確保語言已初始化
return this.languageService.language$.pipe(
map(event => {
const supportedLanguages = ['en-us', 'zh-tw', 'zh-cn'];
return supportedLanguages.includes(event.lang);
})
);
}
}
5. 最佳實踐
記憶體管理
// ✅ 正確:使用 takeUntil 模式
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
export class MyComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
this.languageService.language$
.pipe(takeUntil(this.destroy$))
.subscribe(event => {
// 處理邏輯
});
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
錯誤處理
// ✅ 正確:處理錯誤
this.languageService.language$.subscribe({
next: (event) => {
console.log('語言改變:', event.lang);
},
error: (error) => {
console.error('語言服務錯誤:', error);
},
complete: () => {
console.log('語言監聽完成');
}
});
共享訂閱
// ✅ 正確:使用 shareReplay 避免重複訂閱
import { shareReplay } from 'rxjs/operators';
// 在服務中
export class SharedDataService {
sharedLanguage$ = this.languageService.language$.pipe(
shareReplay(1) // 共享最新的值給所有訂閱者
);
constructor(private languageService: LanguageService) {}
}
6. 常見錯誤和解決方案
錯誤 1: 忘記取消訂閱
// ❌ 錯誤:造成記憶體洩漏
export class BadComponent {
ngOnInit() {
this.languageService.language$.subscribe(event => {
// 組件銷毀後訂閱仍然存在
});
}
}
// ✅ 正確:正確取消訂閱
export class GoodComponent implements OnDestroy {
private subscription?: Subscription;
ngOnInit() {
this.subscription = this.languageService.language$.subscribe(event => {
// 處理邏輯
});
}
ngOnDestroy() {
this.subscription?.unsubscribe();
}
}
錯誤 2: 在訂閱中修改狀態
// ❌ 錯誤:在訂閱回調中修改外部狀態
export class BadComponent {
currentLang = 'en-us';
ngOnInit() {
this.languageService.language$.subscribe(event => {
this.currentLang = event.lang;
// 這裡不應該有複雜邏輯
});
}
}
// ✅ 正確:保持訂閱回調簡單
export class GoodComponent {
currentLang$ = this.languageService.language$.pipe(
map(event => event.lang)
);
constructor(private languageService: LanguageService) {}
}
7. 測試中的使用
// 在測試中使用
describe('MyComponent', () => {
let languageServiceSpy: jasmine.SpyObj<LanguageService>;
beforeEach(() => {
const spy = jasmine.createSpyObj('LanguageService', [], {
language$: of({ lang: 'en-us' } as LangChangeEvent)
});
languageServiceSpy = spy;
});
it('應該訂閱語言變化', () => {
// 測試訂閱邏輯
});
});
總結
使用原則
- 總是取消訂閱 - 防止記憶體洩漏
- 使用 Async Pipe - 在模板中自動管理訂閱
- 保持訂閱簡單 - 複雜邏輯放在 pipe 操作符中
- 處理錯誤 - 總是處理可能的錯誤情況
- 共享熱流 - 使用 shareReplay 優化性能
選擇使用方式
- 組件: Async Pipe 或 takeUntil 模式
- 服務: 直接訂閱(服務通常是單例)
- 指令/Guard: takeUntil 模式
- 測試: 使用測試替身 (spy)
d:\wpg\shipment-notice-platform-web\using-language-observable.md