Unsubscribe - zr贸b to poprawnie!

Pobranie danych ze strumieni (Observable) wi膮偶e si臋 bezpo艣rednio z subskrypcj膮. W momencie, gdy posiadamy wiele Observable i nas艂uchujemy ka偶dy z nich mo偶emy pogorszy膰 dzia艂anie naszej aplikacji.

Gdy ju偶 nie potrzebujemy pobiera膰 danych z Observable to nale偶y usun膮膰 subskrypcje. Wp艂ynie to na polepszenie wydajno艣ci. W aplikacjach Angularowych najlepszym momentem na rezygnacj臋 z nas艂uchiwania b臋dzie moment, w kt贸rym usuwanym komponent.

W tym wpisie przedstawi臋 trzy sposoby i jeden bonus na usuni臋cie subskrypcji z Observable.

Spos贸b 1 - Przypisanie Subscription do zmiennej

Wi臋kszo艣膰 developer贸w zaczyna w艂a艣nie z ta metod膮. Jest to jeden ze sposob贸w, ale nie koniecznie najlepszy. Przyjrzyjmy si臋 jak to wygl膮da w kodzie.

export class Component implements OnInit, OnDestroy {
  sub: Subscription;

  constructor(
    public service: SomeService
  ) {}

  ngOnInit() {
    this.sub = this.service.observable.subscribe(x => console.log(x));
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }
}

W momencie, gdy wywo艂amy metod臋 do nas艂uchiwania (subscribe()) Observable zwr贸ci nam Subscription. Tak膮 klas臋 przypisujemy do zmiennej klasy komponentu.

Zaprzestanie subskrypcji nast臋puje po skasowaniu komponentu. Dlatego u偶ywamy interfejsu OnDestroy, 偶eby wykry膰 takie zdarzenie. W trakcie usuwania komponentu wywo艂ujemy metod臋 unsubscribe() na zmiennej Subscription.

Sprawa komplikuje si臋, gdy posiadamy wi臋cej Subscription. W takim przypadku powinni艣my stworzy膰 wi臋cej zmiennych przypisany do komponentu. Nie jest to dobre rozwi膮zanie. W taki przypadku u偶yjemy sposobu nr 2.

Spos贸b 2 - U偶ycie Subject i operatora takeUntil

Teraz b臋dziemy posiadali jeden 'wy艂膮cznik' i dodamy go do wielu Subscription. Taki 'wy艂膮cznik' b臋dzie sterowany za pomoc膮 Subject i dodawany do Subscription za pomoca operatora takeUntil. Sp贸jrzmy na kod.

export class Component implements OnInit, OnDestroy {
  private destroy$ = new Subject();

  constructor(
    public service: SomeService
  ) {}

  ngOnInit() {
    this.service.observable1
            .pipe(takeUntil(this.destroy$))
            .subscribe(x => console.log(x));
    this.service.observable2
            .pipe(takeUntil(this.destroy$)
            .subscribe(x => console.log(x));
  }

  ngOnDestroy() {
    this.destroy$.next(null);
    this.destroy$.complete();
  }
}

Nasz komponent posiada zmienn膮 typu Subject o nazwie destroy$. Gdy komponent jest usuwany w metodzie ngOnDestroy() zmienna destroy$ wysy艂a tak膮 informacje.

Do ka偶dego Subscription mo偶emy doda膰 operator takeUntil, kt贸ry jest odpowiedzialny za odsubskrybowanie. Gdy tylko destroy$ da zna膰, 偶e usuwamy komponent, operator takeUntil zako艅czy Subscription.

W ten spos贸b mo偶emy obs艂u偶y膰 jedna zmienn膮 Subject wiele subskrypcji. Jest jeszcze jeden spos贸b na zako艅czenie wielu subskrypcji przedstawiony w sposobie nr 3.

Spos贸b 3 - U偶ycie gotowej biblioteki @ngneat/until-destroy

W tym sposobie skorzysta艂em z bilbioteki stworzonej przez bardzo do艣wiadczonego programist臋 Angular Netanel Basal. Wykorzystuje on dekoratory typescript i operatory RxJS.

import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';

@UntilDestroy()
@Component({...})
export class Component implements OnInit {

  constructor(
    public service: SomeService
  ) {}

  ngOnInit() {
    this.service.observable1.pipe(untilDestroyed(this)).subscribe(x => console.log(x));
    this.service.observable2.pipe(untilDestroyed(this)).subscribe(x => console.log(x));
  }
}

Nad dekoratorem komponentu dodajemy dekorator @UntilDestroy. Dzi臋ki temu mo偶emy skorzysta膰 z customowego operatora untilDestroyed.

W tym sposobie znacznie skr贸cili艣my nasz kod. Nie musimy ju偶 korzysta膰 z interfejsu ngOnDestroy. Jest on obs艂ugiwany przez dodany wcze艣niej dekorator @UntilDestroy().

BONUS: Je艣li tylko mo偶esz u偶yj async Pipe!

Dok艂adnie, najlepszy sposobem jest nieu偶ywanie subscribe(). Je艣li mo偶emy to u偶yjmy async pipe i b臋dziemy musieli si臋 martwi膰 problemem z wydajno艣ci膮. Dodatkowo kod b臋dzie bardziej czytelny.

export class Component implements OnInit {
  obs$: Observable;

  constructor(
    public service: SomeService
  ) {}

  ngOnInit() {
    this.obs$ = this.service.observable;
  }
}
<div *ngIf="obs$|async as obs">
    {{ obs | json }}
</div>

Zdecydowanie jest tutaj najwi臋kszy porz膮dek i najbardziej czytelny kod. Wi臋c je艣li nast臋pnym razem b臋dziesz musia艂 u偶y膰 subscribe() to zastan贸w si臋, czy nie lepiej u偶y膰 async pipe.

Podsumowanie

Osobi艣cie rekomenduje u偶ycie async pipe. Je艣li ju偶 b臋dziecie zmuszeni do subskrybowania strumieni to polecam spos贸b nr 3 z wykorzystanie biblioteki @ngneat/until-destroy. Tym sposobem piszemy mniej kodu i mo偶emy obs艂ugiwa膰 wiele Subscription. Przy okazji nasz kod staje si臋 bardziej czytelny.

Copyright 漏 2021 DevLuk