typescript - Dengan RxJs dalam Angular adakah saya hanya perlu menyelesaikan Observables yang dibuat di dalam komponen?
Pada dasarnya saya masih tidak begitu pasti mengenai pembersihan kod reaktif dalam komponen Angular. Adakah peraturan berikut betul dan mencukupi?
Peraturan saya untuk komponen reaktif:
pada manual x.subscribe():
- jika menggunakan x.takeUntil(...) atau konstruksi serupa maka tiada pembersihan manual
- jika x.complete() dipanggil dalam ruang skop komponen maka tiada pembersihan manual
- sebaliknya manual .unsubscribe() bila perlu atau dalam ngOnDestroy()
pada templat observable | async:
- tiada pembersihan manual diperlukan serta serasi dengan onPush
pada penciptaan langsung manual Observable seperti new Subject():
- manual .complete() dalam ngOnDestroy() jika tidak, tidak akan pernah ditutup
mengenai penciptaan manual menggunakan Observable lain seperti Observable.merge(...):
- tiada pembersihan manual diperlukan selagi langganan dibersihkan
Saya tidak begitu pasti tentang titik peluru terakhir.
Berikut adalah contoh penuh bagi komponen sebenar yang saya ubah suai mengikut peraturan tersebut, adakah sesiapa boleh melihat sama ada terdapat risiko kebocoran memori? Semua $ observables awam dilanggan melalui async pipe dalam templat jadi saya pasti langganan tersebut diuruskan. Subjek yang dibuat dengan new dibersihkan secara manual jadi itu sepatutnya OK. Bahagian yang saya risaukan adalah observables Observable.combineLatest(...).
import { ChangeDetectionStrategy, Component, Input, OnDestroy } from '@angular/core';
import { Router } from '@angular/router';
import { DeliveryPointId } from 'app/bso';
import { BookmarkService } from 'app/general';
import { EAccess, EPermission, ReduxGetters, ReduxService } from 'app/redux';
import * as routing from 'app/routing';
import { BehaviorSubject, Observable } from 'app/rx';
@Component({
selector: 'app-last-opened-workitems-widget',
templateUrl: './last-opened-workitems-widget.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LastOpenedWorkitemsWidgetComponent implements OnDestroy {
private readonly showPartners$ = new BehaviorSubject(true);
private readonly showServices$ = new BehaviorSubject(true);
private readonly showTens$ = new BehaviorSubject(true);
private readonly showWorklist$ = new BehaviorSubject(true);
@Input() set showItems(val: boolean) { this.showWorklist$.next(!!val); }
@Input() set showPartners(val: boolean) { this.showPartners$.next(!!val); }
@Input() set showProcesses(val: boolean) { this.showServices$.next(!!val); }
@Input() set showTens(val: boolean) { this.showTens$.next(!!val); }
constructor(
private readonly redux: ReduxService,
private readonly bookmarks: BookmarkService,
private readonly router: Router,
) { }
canPartners$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Partner] >= EAccess.Read);
canServices$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Services] >= EAccess.Read);
canTens$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Tens] >= EAccess.Read);
canWorklist$ = this.redux.watch(ReduxGetters.userAccess).map(access => access[EPermission.Worklist] >= EAccess.Read);
lastItems$ = this.bookmarks.onLastOpenedWorkitems.map(ii => [...ii].reverse());
lastPartners$ = this.bookmarks.onLastOpenedPartners.map(ii => [...ii].reverse());
lastProcesses$ = this.bookmarks.onLastOpenedProcesses.map(ii => [...ii].reverse());
lastTens$ = this.bookmarks.onLastOpenedTens.map(ii => [...ii].reverse());
hasContentPartners$ = Observable
.combineLatest(
this.showPartners$.distinctUntilChanged(),
this.canPartners$,
this.lastPartners$.map(ii => ii.length > 0))
.map(oks => oks.every(ii => ii));
hasContentServices$ = Observable
.combineLatest(
this.showServices$.distinctUntilChanged(),
this.canServices$,
this.lastProcesses$.map(ii => ii.length > 0))
.map(oks => oks.every(ii => ii));
hasContentTens$ = Observable
.combineLatest(
this.showTens$.distinctUntilChanged(),
this.canTens$,
this.lastTens$.map(ii => ii.length > 0))
.map(oks => oks.every(ii => ii));
hasContentWorklist$ = Observable
.combineLatest(
this.showWorklist$.distinctUntilChanged(),
this.canWorklist$,
this.lastItems$.map(ii => ii.length > 0))
.map(oks => oks.every(ii => ii));
hasContent$ = Observable
.combineLatest(this.hasContentPartners$, this.hasContentServices$, this.hasContentTens$, this.hasContentWorklist$)
.map(oks => oks.some(ii => ii));
ngOnDestroy() {
[this.showPartners$, this.showServices$, this.showTens$, this.showWorklist$].forEach(ii => ii.complete());
}
gotoPartner = (id: string) => routing.gotoPartnerItem(this.router, id);
gotoProcess = (id: number) => routing.gotoProcess(this.router, id);
gotoTensItem = (id: DeliveryPointId) => routing.gotoTensItem(this.router, id);
gotoWorkitem = (id: number) => routing.gotoWorkitem(this.router, id);
}
Jawapan #1
Anda perlu membatalkan langganan setiap dan semua langganan anda, contohnya dalam kaedah ngOnDestroy().
Atau anda boleh 'hadkan' observables menggunakan operator seperti takeUntil(), take(), first() dan sebagainya.
Peraturan di atas juga sah untuk combineLatest.
Jangan risau tentang langganan yang dicipta oleh AsyncPipe kerana AsyncPipe itu sendiri bertanggungjawab untuk berhenti melanggan langganannya sendiri.
Bacaan yang baik mengenai topik ini ialah artikel oleh Ben Lesh yang merupakan ketua RxJS.
Jawapan #2
OK saya sebenarnya baru sahaja mengujinya dengan .finally(...) serta log untuk semua Observable pertengahan dan ia dipicu dengan betul apabila komponen dimusnahkan.
Oleh itu saya mengisytiharkan peraturan di atas sebagai betul dan mencukupi.