Angular. Способы отписываться от Observable
Вместо тысячи слов
Руками через свойство класса
Одна подписка в одно свойство. Имеет смысл, когда необходимо переподписываться в ходе жизненного цикла компонента, например в сеттере или ngOnChanges
.
@Component()
export class ManualComponent implements OnInit, OnDestroy {
private subscription = Subscription.EMPTY;
ngOnInit() {
this.subscription = interval(1000).subscribe(observer('manual'));
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
Вложенные подписки в свойстве класса
При отписки родителя отписываются все наследники.
@Component()
export class AddComponent implements OnInit, OnDestroy {
readonly subscription = new Subscription();
ngOnInit() {
this.subscription.add(interval(900).subscribe(observer('add 1')));
this.subscription.add(interval(1200).subscribe(observer('add 2')));
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
}
Стоит отметить, что данный способ не подходит для случаев переподписки.
Массив подписок в свойстве класса
@Component()
export class ArrayComponent implements OnInit, OnDestroy {
private subscriptionList: Subscription[] = [];
set sub(sub: Subscription) {
this.subscriptionList.push(sub);
}
ngOnInit() {
this.subscriptionList.push(interval(1200).subscribe(observer('array 1')));
// or
this.sub = interval(900).subscribe(observer('array 2'));
}
ngOnDestroy(): void {
this.subscriptionList.forEach(s => s.unsubscribe());
}
}
takeUntil
Самый простой декларативный способ отписки
@Component()
export class TakeUntilComponent implements OnInit, OnDestroy {
private destroy$ = new Subject<void>();
ngOnInit() {
interval(900)
.pipe(takeUntil(this.destroy$))
.subscribe(observer('take-until 1'));
interval(1200)
.pipe(takeUntil(this.destroy$))
.subscribe(observer('take-until 2'));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
DestroyService
Вариация декларативного способа отписки от ребят из Тинькофф. Преимущество над простым способом в том, что не нужно каждый раз добавлять ngOnDestroy
. Может помочь в ситуации, когда нужно отписаться внутри DI фабрики.
/**
* @see https://github.com/TinkoffCreditSystems/taiga-ui
* Observable abstraction over ngOnDestroy to use with takeUntil
*/
@Injectable()
export class DestroyService extends Subject<void> implements OnDestroy {
ngOnDestroy() {
this.next();
this.complete();
}
}
@Component({
...
providers: [DestroyService]
})
export class DestroyServiceComponent implements OnInit {
constructor(private destroy$: DestroyService) {}
ngOnInit() {
interval(900)
.pipe(takeUntil(this.destroy$))
.subscribe(observer('destroy-service 1'));
}
}
UntilDestroy
https://github.com/ngneat/until-destroy Использую в своих приложениях и на работе. Сомнительный выбор для распространяемых библиотек так как лишняя зависимость.
Улучшает 3 варианта:
- Подписка в свойстве
- Подписки в массиве
- takeUntil
Преимущество в том, что не нужно каждый раз добавлять ngOnDestroy
@UntilDestroy({ checkProperties: true })
@Component()
export class UntilDestroyedManualComponent implements OnInit {
private subscription = Subscription.EMPTY;
ngOnInit() {
this.subscription = interval(1000).subscribe(
observer('until-destroyed-manual')
);
this.subscription.add(
interval(1200).subscribe(observer('until-destroyed-add'))
);
}
}
@UntilDestroy({ arrayName: 'subscriptionList' })
@Component()
export class UntilDestroyedArrayComponent implements OnInit {
private subscriptionList: Subscription[] = [];
set sub(sub: Subscription) {
this.subscriptionList.push(sub);
}
ngOnInit() {
this.subscriptionList.push(
interval(1200).subscribe(observer('until-destroyed-array 1'))
);
// or
this.sub = interval(900).subscribe(observer('until-destroyed-array 2'));
}
}
@UntilDestroy()
@Component()
export class UntilDestroyedComponent implements OnInit {
ngOnInit() {
interval(900)
.pipe(untilDestroyed(this))
.subscribe(observer('until-destroyed 1'));
interval(1200)
.pipe(untilDestroyed(this))
.subscribe(observer('until-destroyed 2'));
}
}
Вместо послесловия
Если можно отдать подписку в шаблон, то лучше так и сделать. Если нельзя, то по возможности сделать декларативно, в приложении через UntilDestroy, в библиотеке - через takeUntil и его вариации. Для случаев переподписки подойдет любой вариант. И не забывать, что в providedIn: 'root'
сервисах нельзя полагаться на вызов ngOnDestroy
.