---
# System prepended metadata

title: Day24 【牙起來】 訂閱Subscribe、被訂閱者Observable - RxJS

---

# Day24 【牙起來】 訂閱Subscribe、被訂閱者Observable - RxJS


> `Observable`中文直譯為**可觀測物件**
> 我比較喜歡稱他為 **被訂閱者**，簡單又直觀

---

## 訂閱與被訂閱

訂閱的世界裡有**三個名詞**

* `Subscriber` 訂閱者： 可以訂閱別人
* `Observable` 被訂閱者： 可以被訂閱
* `Subscribe` 訂閱： 動詞，訂閱本身的行為


每當**被訂閱者**完成一部影片，會發出通知，
告訴所有**訂閱**他的**訂閱者**，說：「欸欸我出一個新的影片囉，快叫大家來看！」

 
> Subscriber 訂閱者 -> (Subscribe) -> Observable 被訂閱者
>
> 中文為： 『訂閱者 訂閱了 被訂閱者』
> 這整個過程則稱為 **訂閱事件**

---

## 程式裡的被訂閱者

### 方法一、直接訂閱的形式

依照上一次的範例來看，在`store.component.ts`中

```typescript=
  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';
    this.http.get(url).subscribe(result => {
      console.log(result);
    });
  }
```

* 藍框內 `this.http.get(url)` 代表**被訂閱者**
* 橘框 `.subscribe()` 表示**訂閱的動作**

![](https://i.imgur.com/YrutNzT.png)

`subscribe()` 的裡面則是一個`callBack`函式，把回來的資料`result`給印出來

### 方法二、給個變數直接訂閱

也可以寫成這樣，讓變數`getData$`成為`Observable`
最後再`subscribe()`他

```typescript=
export class StoreComponent {

  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';

    let getData$ = this.http.get(url);
    getData$.subscribe(
      result => {
        console.log(result);
      }
    )
  }

}
```


> 在變數命名慣例上，會對`Observable`物件尾端加上錢字號 `$` 
> 
> 後綴`$`能讓人們意會到這個物件是**資料流**，而非資料本身

### 方法三、統一管理的形式

也可以長成這樣子
使用`Subscription`物件來集中管理所有的 **訂閱事件**

```typescript=
export class StoreComponent {

  subscription = new Subscription();
  
  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';

    let getData$ = this.http.get(url);
    
    this.subscription.add(
      getData$.subscribe(
        result => {
          console.log(result);
        }
      )
    )
  }

}
```

但這個樣子形式還是有點醜，似乎跟原本一樣，沒有什麼意義
所以我們可以再加上 `RxJS` 的 `.pipe()` 方法，把要處理的事情都丟進去

`.tap()` 就是在`pipe()`管線中要他執行某些事情 **Do Something** 的意思

把要做的事情、醜醜的code都包裝起來，對外僅露出短短的一行`getData$.subscribe()`

```typescript=
export class StoreComponent {

  subscription = new Subscription();

  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';

    let getData$ = this.http.get(url).pipe(
      tap(
        result => {
          console.log(result);
        }
      )
    );

    this.subscription.add(getData$.subscribe());

  }

}
```

或者這樣，
先將資料流`subscribe()`，再放進`Subscription`物件中

```typescript=
export class StoreComponent {

  subscription = new Subscription();

  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';

    let getData$ = this.http.get(url).pipe(
      tap(
        result => {
          console.log(result);
        }
      )
    ).subscribe();

    this.subscription.add(getData$);

  }

}
```

為什麼推薦這樣子寫呢？
因為等專案大一點之後，如果用**方法一**
可能會看到類似如下的程式碼


#### 不小心就做了多次訂閱

```typescript=
  constructor(private http: HttpClient) {
    let url = 'https://63403667d1fcddf69cb402b7.mockapi.io/api/v1/weapons';

    let getData$ = this.http.get(url);

    this.subscription.add(
      getData$.subscribe(
        result => {
          console.log(result);
        }
      )
    )

      ...

    this.subscription.add(
      getData$.subscribe(
        result => {
          console.log(result);
        }
      )
    )

  }

```

運行結果
![](https://i.imgur.com/8nZS8O9.png)


而使用`Subscription`物件

#### 對同一個資料流新增多次訂閱事件，也只會做一次訂閱

```typescript=
this.subscription.add(getData$);
this.subscription.add(getData$);
this.subscription.add(getData$);
```

運行結果
![](https://i.imgur.com/9UmLjvO.png)


把訂閱集中、收集起來，不僅能確保**只做了一次訂閱**
對於後續接手的人來說，也能很明確知道這個元件的程式內
總共對哪些資料流做了訂閱，可以一目瞭然

```typescript=
this.subscription.add(getData1$);
this.subscription.add(getData2$);
this.subscription.add(getData3$);
```





