﻿# 在 Angular 專案中 RxJS 實現 Unsubscribe 取消訂閱的四種常見方法

RxJS 可以將所有 Observable 物件簡單區分成兩種不同類型的，一種是**有限事件數量**的 Observable 物件，例如 HttpClient 相關 API 在訂閱之後就只會有一筆資料回來，這種類型的 Observable 在訂閱之後是不需要特別取消訂閱的。另一種則是**無限事件數量**的 Observable 物件，像是 DOM 的事件訂閱，或是使用 RxJS 的 [timer](https://rxjs.dev/api/index/function/timer) 建立運算子(Creation Operators)，或是你在元件中訂閱 [Router.events](https://angular.io/api/router/Router#events) 等等，這些 Observable 都沒有結束的一天，因此需要實作**取消訂閱**(Unsubscribe)，否則就可能會導致記憶體洩漏等問題。這篇文章我將分享四種不同的 RxJS 取消訂閱方法。

> 在從 Angular 元件注入 [ActivatedRoute](https://angular.io/api/router/ActivatedRoute) 的時候，該物件裡面有許多屬性都是 `Observable<T>` 型別，這些 Observable 在訂閱之後，其實是 **無限事件數量** 的 Observable，但因為這些屬性都與當前頁面的路由有關，當頁面跳轉到其他路由後，所有 Observable 的資料來源會自動關閉，因此在這些 Observable 在訂閱之後都是不需要特別取消訂閱的，算是個特例！

### 第一種：傳統的取消訂閱方法 (Subscription)

當你的 Angular 元件中沒有太多 Observable 需要在訂閱之後取消訂閱的話，就可以用這種最傳統的 RxJS 取消訂閱方法。

大致上這個方法包含以下步驟：

1.  宣告一個 [Subscription](https://rxjs.dev/guide/subscription) 屬性
    
    ```typescript
    subscription: Subscription;
    ```
    
2.  訂閱時取得 [Subscription](https://rxjs.dev/guide/subscription) 物件
    
    ```typescript
    this.subscription = observable.subscribe(observer);
    ```
    
3.  元件摧毀時對 [Subscription](https://rxjs.dev/guide/subscription) 物件取消訂閱
    
    ```typescript
    ngOnDestroy(): void {
      this.subscription.unsubscribe();
    }
    ```
    

> 範例程式: [Angular + RxJS: Unsubscribe Method 1](https://stackblitz.com/edit/angular-rxjs-unsubscribe-1?file=src%2Fapp%2Fhello.component.ts)

### 第二種：現代的取消訂閱方法 (Subject + takeUntil)

當你一個 Angular 元件中有許多 Observable 需要在訂閱之後取消訂閱的話，就可以考慮用 [Subject](https://rxjs.dev/guide/subject) + [takeUntil](https://rxjs.dev/api/operators/takeUntil) 的應用技巧批次將所有 Subscription 取消訂閱。

大致上這個方法包含以下步驟：

1.  宣告一個 [Subject](https://rxjs.dev/guide/subject) 屬性
    
    ```typescript
    destroy$ = new Subject();
    ```
    
2.  訂閱時搭配 `.pipe()` 與 `takeUntil(this.destroy$)` 讓 [Observable](https://rxjs.dev/guide/observable) 與 [Subject](https://rxjs.dev/guide/subject) 進行連動
    
    ```typescript
    observable.pipe(takeUntil(this.destroy$)).subscribe(observer);
    ```
    
3.  元件摧毀時對 [Subject](https://rxjs.dev/guide/subject) 發送一個串流資料並設定完成
    
    ```typescript
    ngOnDestroy(): void {
      this.destroy$.next(null);
      this.destroy$.complete();
    }
    ```
    

> 範例程式: [Angular + RxJS: Unsubscribe Method 2](https://stackblitz.com/edit/angular-rxjs-unsubscribe-2?file=src%2Fapp%2Fhello.component.ts)

### 第三種：進階的取消訂閱方法 (UntilDestroy)

當你很多 Angular 元件中有許多 Observable 需要在訂閱之後取消訂閱，若不想在每個元件加入 `ngOnDestroy()` 方法的話，那可以考慮使用 [@ngneat/until-destroy](https://web.archive.org/web/20251024200229/https://github.com/ngneat/until-destroy) 套件，此套件使用到自訂的 [Decorator](https://oldmo860617.medium.com/%E5%8D%81%E5%88%86%E9%90%98%E5%B8%B6%E4%BD%A0%E4%BA%86%E8%A7%A3-typescript-decorator-48c2ae9e246d) 方法，自動替你的訂閱物件加入「取消訂閱」功能。

大致上這個方法包含以下步驟：

1.  直接在 `@Component()` 之上外加一個 `@UntilDestroy()` 裝飾器 (Decorator)
    
    ```typescript
    @UntilDestroy()
    @Component({
      selector: 'hello',
      template: `<h1>Hello {{counter}}!</h1>`,
      styles: [`h1 { font-family: Lato; }`]
    })
    export class HelloComponent implements OnInit { ... }
    ```
    
2.  訂閱時搭配 `.pipe(untilDestroyed(this))` 就可以讓元件摧毀時自動取消訂閱（不用額外儲存 Subscription 物件）
    
    ```typescript
    observable.pipe(untilDestroyed(this)).subscribe(observer);
    ```
    

> 範例程式: [Angular + RxJS: Unsubscribe Method 3-1](https://stackblitz.com/edit/angular-rxjs-unsubscribe-3-1?file=src%2Fapp%2Fhello.component.ts)

這個套件還有另外一種神奇用法，一樣可以不用替每個元件加入 `ngOnDestroy()` 方法，而且你只要在元件類別中宣告 `Subscription` 型別的屬性即可在元件摧毀時自動取消訂閱。

大致上這個方法包含以下步驟：

1.  直接在 `@Component()` 之上外加一個 `@UntilDestroy({ checkProperties: true })` 裝飾器 (Decorator)
    
    ```typescript
    @UntilDestroy({ checkProperties: true })
    @Component({
      selector: 'hello',
      template: `<h1>Hello {{counter}}!</h1>`,
      styles: [`h1 { font-family: Lato; }`]
    })
    export class HelloComponent implements OnInit { ... }
    ```
    
2.  宣告一個 Subscription 屬性
    
    ```typescript
    subscription: Subscription;
    ```
    
3.  只要將 Subscription 物件存起來即可（不用額外使用 `.pipe(untilDestroyed(this))` 運算子）
    
    ```typescript
    this.subscription = observable.subscribe(observer);
    ```
    

> 範例程式: [Angular + RxJS: Unsubscribe Method 3-2](https://stackblitz.com/edit/angular-rxjs-unsubscribe-3-2?file=src%2Fapp%2Fhello.component.ts)

### 第四種：直接使用 AsyncPipe 訂閱任何 Observable 物件

若你的 Observable 資料最終需要顯示在畫面上，你或許可以認真考慮直接從 Template 使用 [AsyncPipe](https://angular.io/api/common/AsyncPipe) 訂閱 Observable 物件，因為所有使用 AsyncPipe 訂閱的物件，最終都會在元件摧毀時自動取消訂閱，非常方便！

大致上這個方法包含以下步驟：

1.  宣告一個 Observable 屬性
    
    ```typescript
    counter$!: Observable<number>;
    ```
    
2.  準備好必要的 Observable 物件，必要時可加入 `.pipe()` 運算子
    
    ```typescript
    let observable = interval(1000);
    
    this.counter$ = observable.pipe(
      tap((i) => console.log('counter = ', i)),
      startWith(0),
    );
    ```
    
3.  直接在 Template 透過 [AsyncPipe](https://angular.io/api/common/AsyncPipe) 訂閱 [Observable](https://rxjs.dev/guide/observable) 物件（完全不用擔心何時要取消訂閱）
    
    ```typescript
    <h1>Hello {{counter$ | async}}!</h1>
    ```
    
    如果寫在 `*ngIf` 上，可以利用 `as` 取得一個區域變數：
    
    ```typescript
    <h1 *ngIf="counter$ | async as num">Hello {{num}}!</h1>
    ```
    

> 範例程式: [Angular + RxJS: Unsubscribe Method 4-1](https://stackblitz.com/edit/angular-rxjs-unsubscribe-4-1?file=src%2Fapp%2Fhello.component.ts)

> 範例程式: [Angular + RxJS: Unsubscribe Method 4-2](https://stackblitz.com/edit/angular-rxjs-unsubscribe-4-2?file=src%2Fapp%2Fhello.component.ts)

### 相關連結

-   [RxJS - Subscription](https://rxjs.dev/guide/subscription)
-   [RxJS - Subject](https://rxjs.dev/guide/subject)
-   [ngneat/until-destroy: 🦊 RxJS operator that unsubscribe from observables on destroy](https://web.archive.org/web/20251024200229/https://github.com/ngneat/until-destroy)
-   [Angular - AsyncPipe](https://angular.io/api/common/AsyncPipe)
-   [Best Practices for Managing RxJS Subscriptions](https://www.thisdot.co/blog/best-practices-for-managing-rxjs-subscriptions)
