# Gestion de la Subscription ⚠️

Dans les exemples ci-dessus, l'objet `Subscription` retourné par la méthode `subscribe` est simplement ignoré.

{% hint style="success" %}
Il faut s'assurer que **le composant `unsubscribe` de l'`Observable` avant sa destruction** *(et également si une nouvelle requête est déclenchée par exemple en cas de "refresh")*.

Dans le cas d'`Observable`s infinis cela **évite des fuites mémoire et surconsommation CPU**.

Dans notre cas, cela **évite la congestion des requêtes HTTP** *(dans le cas où le composant est détruit et reconstruit plusieurs fois rapidement par exemple ou encore lors de la navigation sur l'application via le* [*Routing*](https://guide-angular.wishtack.io/angular/routing)*).*
{% endhint %}

## `Unsubscribe` dans `ngOnDestroy`

On profite généralement du "Lifecycle Hook" `ngOnDestroy` pour déclencher l'`unsubscribe`.

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
private _bookListSubscription: Subscription;

constructor(private _bookRepository: BookRepository) {
}

ngOnInit() {
    this._bookListSubscription = this._bookRepository.getBookList()
        .subscribe(bookList => this.bookList = bookList);
}

ngOnDestroy() {
    this._bookListSubscription.unsubscribe();
}
```

{% endtab %}
{% endtabs %}

Et par précaution dans le cas où la `subscription` est créé plus tard.

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
ngOnDestroy() {
    if (this._bookListSubscription != null) {
        this._bookListSubscription.unsubscribe();
    }
}
```

{% endtab %}
{% endtabs %}

**L'inconvénient** de cette approche est sa **verbosité**. **Elle en devient "error-prone"**.

## `Unsubscribe` avec l'opérateur `takeUntil`

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
export class BookSearchComponent implements OnDestroy, OnInit {

    bookList: Book[];

    private _isDead$ = new Subject();

    constructor(private _bookRepository: BookRepository) {
    }

    ngOnInit() {
        this._bookRepository.getBookList()
            .pipe(takeUntil(this._isDead$))
            .subscribe(bookList => this.bookList = bookList);
    }

    ngOnDestroy() {
        this._isDead$.next();
    }

}
```

{% endtab %}
{% endtabs %}

{% hint style="warning" %}
Il existe une approche similaire avec l'opérateur **`takeWhile`** qui se base sur un valeur "boolean" plutôt qu'un `Observable` mais il est **préférable de l'éviter**.

Contrairement à l'approche `takeUntil`, `takeWhile` ne peut pas détecter en temps réel le changement de la variable "boolean" et **la requête HTTP ne sera donc pas interrompue**.
{% endhint %}

### Exemple

<https://github.com/wishtack/wishtack-book-shop/tree/7-unsubscribe-using-take-until>

{% embed url="<https://stackblitz.com/github/wishtack/wishtack-book-shop/tree/7-unsubscribe-using-take-until>" %}

## Unsubscribe avec le [Pipe](https://guide-angular.wishtack.io/angular/pipes) `async`

Dans les cas les plus simples, cette approche est la plus adaptée car c'est **la moins verbeuse** est **la plus réactive**.

Le "pipe" `async` permet de `préparer l'Observable dans le composant` et laisser **la vue `subscribe` quand elle en a besoin et si elle en a besoin**.

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
export class BookSearchComponent {

    bookList$: Observable<Book[]>;

    constructor(private _bookRepository: BookRepository) {
        this.bookList$ = this._bookRepository.getBookList();
    }

}
```

{% endtab %}
{% endtabs %}

Remarquez que l'**on se permet de créer l'`Observable` directement dans le constructeur**. En effet, tant que l'on ne `subscribe` pas, aucun traitement n'est déclenché.

### A éviter

{% hint style="warning" %}
Il est possible d'utiliser la syntaxe perturbante ci-dessous mais il est préférable de l'éviter.
{% endhint %}

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
export class BookSearchComponent {

    bookList$ = this._bookRepository.getBookList();

    constructor(private _bookRepository: BookRepository) {
    }

}
```

{% endtab %}
{% endtabs %}

### Fonctionnement du "pipe" `async`

{% tabs %}
{% tab title="book-search.component.html" %}

```markup
<wt-book-preview
        *ngFor="let book of bookList$ | async"
        [book]="book"></wt-book-preview>
```

{% endtab %}
{% endtabs %}

Le "pipe" `async` **`subscribe` à l'`Observable`** **`bookList$`** et **permet de mettre à jour la vue en conséquence**.

A la destruction de l'élément *(e.g. : "toggle" de la liste via `*ngIf`)*, **le "pipe" `async`** **`unsubscribe` automagiquement**.

### Gotcha

En essayant d'afficher le nombre de "book" en créant un autre `Observable` :

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
export class BookSearchComponent {

    bookCount$: Observable<number>;
    bookList$: Observable<Book[]>;

    constructor(private _bookRepository: BookRepository) {
        this.bookList$ = this._bookRepository.getBookList();
        this.bookCount$ = this.bookList$.pipe(map(bookList => bookList.length));
    }

}
```

{% endtab %}
{% endtabs %}

{% tabs %}
{% tab title="book-search.component.html" %}

```markup
<div>{{ bookCount$ | async }}</div>

<wt-book-preview
        *ngFor="let book of bookList$ | async"
        [book]="book"></wt-book-preview>
```

{% endtab %}
{% endtabs %}

{% hint style="danger" %}
Cela "fonctionne" mais on peut remarquer que **la requête HTTP a été exécutée deux fois**.

L'`Observable` créé par le service `HttpClient` est de type "**Cold"** *(c'est à dire Lazy)*.\
Chaque "pipe" `async` appelle la méthode `subscribe` et déclenche donc la requête HTTP puis les traitements appliqués par les opérateurs *(l'opérateur `map` dans notre cas)*.
{% endhint %}

### `shareReplay`

Le comportement souhaité est le suivant :

* **Tant qu'il n'y a aucun appel à `subscribe`** *(explicite ou implicite via `async`)*, le **traitement ne doit pas s'exécuter**.
* Au **premier `subscribe`**, le **traitement** doit être **déclenché**.
* Les **`subscribe`s supplémentaires ne doivent pas redéclencher le traitement** mais simplement attendre le résultat.
* Les souscripteurs qui **arrivent après la récupération** du résultat doivent **récupérer la dernière valeur obtenue**.

L'intégralité de se traitement se fait simplement avec l'opérateur `shareReplay` en lui indiquant en paramètre la taille du buffer de mémorisation. Un buffer de taille 1 va mémoriser la dernière valeur.

Le problème décrit au paragraphe précédent est alors résolu ainsi :

```typescript
export class BookSearchComponent {

    bookCount$: Observable<number>;
    bookList$: Observable<Book[]>;

    constructor(private _bookRepository: BookRepository) {
        this.bookList$ = this._bookRepository.getBookList()
            .pipe(shareReplay(1));
        this.bookCount$ = this.bookList$.pipe(map(bookList => bookList.length));
    }

}
```

### Gestion d'erreurs

Pour capturer les erreurs et détecter la fin du traitement *(i.e. `try` / `catch` / `finally`)*, il suffit d'utiliser les opérateurs `catchError` et `finalize`.

**`EMPTY` est une constante contenant un `Observable` vide**. Vous êtes libres de retourner un `Observable` contenant des données *(de secours)* provenant d'une autre source *(cache etc...)*.

{% tabs %}
{% tab title="book-search.component.ts" %}

```typescript
import { EMPTY, Observable } from 'rxjs';
import { catchError, finalize, map, shareReplay } from 'rxjs/operators';

this.bookList$ = this._bookRepository.getBookList()
    .pipe(
        catchError(error => {
            console.error(error);
            return EMPTY;
        }),
        finalize(() => {
            console.log('Done!');
        }),
        shareReplay(1)
    );
```

{% endtab %}
{% endtabs %}

### Exemple

<https://github.com/wishtack/wishtack-book-shop/tree/8-unsubscribe-using-async-pipe>

{% embed url="<https://stackblitz.com/github/wishtack/wishtack-book-shop/tree/8-unsubscribe-using-async-pipe>" %}

![Observables ❤️async pipe ❤️retry](https://4009647861-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L9vDDYxu6nH7FVBtFFS%2F-LChU6eZ6LUmdpLBnMbn%2F-LChUB9vp3JnrdNU8hY2%2Fhttp-retry.gif?alt=media\&token=8662c8e8-6dd0-4ed9-a598-fb0f07d36a1d)

Pour reproduire l'erreur ci-dessus, vous pouvez **bloquer le domaine de l'API sur Chrome** via le menu décrit ci-dessous.

![Chrome Block request](https://4009647861-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-L9vDDYxu6nH7FVBtFFS%2F-LChU6eZ6LUmdpLBnMbn%2F-LChUN0RPbbXCBh8xbHv%2Fchrome-block-domain-or-url.png?alt=media\&token=6fb07741-d650-4e6e-82d6-ef24010dee33)

## RxScavenger

<https://blog.wishtack.com/2018/05/30/handle-rxjs-subscriptions-properly-using-rx-scavenger/>

{% embed url="<https://blog.wishtack.com/2018/05/30/handle-rxjs-subscriptions-properly-using-rx-scavenger/>" %}
