Angular / RxJs Kapan saya harus berhenti berlangganan dari `Berlangganan`

720

Kapan saya harus menyimpan Subscriptioninstance dan memohon unsubscribe()selama siklus hidup NgOnDestroy dan kapan saya bisa mengabaikannya?

Menyimpan semua langganan menimbulkan banyak kekacauan dalam kode komponen.

Panduan Klien HTTP mengabaikan langganan seperti ini:

getHeroes() {
  this.heroService.getHeroes()
                   .subscribe(
                     heroes => this.heroes = heroes,
                     error =>  this.errorMessage = <any>error);
}

Pada saat yang sama Panduan Rute & Navigasi mengatakan bahwa:

Akhirnya, kami akan menavigasi ke tempat lain. Router akan menghapus komponen ini dari DOM dan menghancurkannya. Kita perlu membersihkan diri kita sendiri sebelum itu terjadi. Secara khusus, kita harus berhenti berlangganan sebelum Angular menghancurkan komponen. Kegagalan untuk melakukannya dapat membuat kebocoran memori.

Kami berhenti berlangganan dari kami Observabledi ngOnDestroymetode.

private sub: any;

ngOnInit() {
  this.sub = this.route.params.subscribe(params => {
     let id = +params['id']; // (+) converts string 'id' to a number
     this.service.getHero(id).then(hero => this.hero = hero);
   });
}

ngOnDestroy() {
  this.sub.unsubscribe();
}
Sergey Tihon
sumber
21
Saya kira Subscriptions http-requestsdapat diabaikan, karena mereka hanya memanggil onNextsekali dan kemudian mereka memanggil onComplete. The Routerbukan panggilan onNextberulang-ulang dan mungkin tidak pernah memanggil onComplete(tidak yakin tentang itu ...). Sama berlaku untuk Observables dari Events. Jadi saya kira itu seharusnya unsubscribed.
Springrbua
1
@ gt6707a Aliran menyelesaikan (atau tidak menyelesaikan) independen dari pengamatan penyelesaian itu. Callback (pengamat) yang disediakan untuk fungsi berlangganan tidak menentukan apakah sumber daya dialokasikan. Ini adalah panggilan untuk subscribedirinya sendiri yang berpotensi mengalokasikan sumber daya hulu.
seangwright

Jawaban:

981

--- Sunting 4 - Sumber Daya Tambahan (2018/09/01)

Dalam episode baru-baru ini dari Adventures in Angular Ben Lesh dan Ward Bell membahas masalah seputar bagaimana / kapan harus berhenti berlangganan suatu komponen. Diskusi dimulai pada sekitar 1:05:30.

Ward menyebutkan right now there's an awful takeUntil dance that takes a lot of machinerydan Shai Reznik menyebutkan Angular handles some of the subscriptions like http and routing.

Sebagai tanggapan, Ben menyebutkan bahwa ada diskusi sekarang untuk memungkinkan Observable untuk terhubung ke peristiwa siklus hidup komponen Angular dan Ward menyarankan Observable peristiwa siklus hidup yang dapat berlangganan sebagai cara untuk mengetahui kapan harus menyelesaikan Observable yang dikelola sebagai keadaan internal komponen.

Yang mengatakan, kita sebagian besar membutuhkan solusi sekarang jadi di sini adalah beberapa sumber

  1. Sebuah rekomendasi untuk takeUntil()pola dari anggota tim inti RxJs Nicholas Jamieson dan aturan tslint untuk membantu menegakkannya. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Paket npm ringan yang mengekspos operator yang dapat diobservasi yang menggunakan instance komponen ( this) sebagai parameter dan secara otomatis berhenti berlangganan selama ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Variasi lain di atas dengan ergonomi yang sedikit lebih baik jika Anda tidak melakukan build AOT (tetapi kita semua harus melakukan AOT sekarang). https://github.com/smnbbrv/ngx-rx-collector

  4. Arahan khusus *ngSubscribeyang berfungsi seperti pipa async tetapi membuat tampilan tertanam di templat Anda sehingga Anda dapat merujuk ke nilai 'tidak terbuka' di seluruh templat Anda. https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

Saya menyebutkan dalam komentar di blog Nicholas bahwa penggunaan berlebihan takeUntil()bisa menjadi pertanda bahwa komponen Anda mencoba melakukan terlalu banyak dan bahwa memisahkan komponen yang ada menjadi komponen Fitur dan Presentasi harus dipertimbangkan. Anda kemudian dapat | asyncdiamati dari komponen Fitur ke Inputdalam komponen Presentational, yang berarti tidak ada langganan yang diperlukan di mana pun. Baca lebih lanjut tentang pendekatan ini di sini

--- Sunting 3 - Solusi 'Resmi' (2017/04/09)

Saya berbicara dengan Ward Bell tentang pertanyaan ini di NGConf (Saya bahkan menunjukkan kepadanya jawaban ini yang menurutnya benar) tetapi dia mengatakan kepada saya bahwa tim dokumen untuk Angular memiliki solusi untuk pertanyaan ini yang tidak dipublikasikan (walaupun mereka sedang berusaha untuk mendapatkan persetujuannya) ). Dia juga mengatakan kepada saya bahwa saya dapat memperbarui jawaban SO saya dengan rekomendasi resmi yang akan datang.

Solusi yang kita semua harus gunakan maju adalah menambahkan private ngUnsubscribe = new Subject();bidang ke semua komponen yang memiliki .subscribe()panggilan ke Observabledalam kode kelas mereka.

Kami kemudian memanggil metode this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();kami ngOnDestroy().

Saus rahasia (sebagaimana telah dicatat oleh @metamaker ) adalah untuk menelepon takeUntil(this.ngUnsubscribe)sebelum masing-masing .subscribe()panggilan kami yang akan menjamin semua langganan akan dibersihkan ketika komponen dihancurkan.

Contoh:

import { Component, OnDestroy, OnInit } from '@angular/core';
// RxJs 6.x+ import paths
import { filter, startWith, takeUntil } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { BookService } from '../books.service';

@Component({
    selector: 'app-books',
    templateUrl: './books.component.html'
})
export class BooksComponent implements OnDestroy, OnInit {
    private ngUnsubscribe = new Subject();

    constructor(private booksService: BookService) { }

    ngOnInit() {
        this.booksService.getBooks()
            .pipe(
               startWith([]),
               filter(books => books.length > 0),
               takeUntil(this.ngUnsubscribe)
            )
            .subscribe(books => console.log(books));

        this.booksService.getArchivedBooks()
            .pipe(takeUntil(this.ngUnsubscribe))
            .subscribe(archivedBooks => console.log(archivedBooks));
    }

    ngOnDestroy() {
        this.ngUnsubscribe.next();
        this.ngUnsubscribe.complete();
    }
}

Catatan: Penting untuk menambahkan takeUntiloperator sebagai yang terakhir untuk mencegah kebocoran dengan yang dapat diamati antara dalam rantai operator.

--- Edit 2 (2016/12/28)

Sumber 5

Tutorial Angular, bab Routing sekarang menyatakan sebagai berikut: "Router mengelola observable yang disediakan dan melokalisasi langganan. Langganan dibersihkan ketika komponen dihancurkan, melindungi terhadap kebocoran memori, jadi kita tidak perlu berhenti berlangganan dari rute params Teramati. " - Mark Rajcok

Berikut ini adalah diskusi tentang masalah Github untuk dokumen Angular mengenai Router Observables di mana Ward Bell menyebutkan bahwa klarifikasi untuk semua ini sedang dikerjakan.

--- Edit 1

Sumber 4

Dalam video ini dari NgEurope Rob Wormald juga mengatakan Anda tidak perlu berhenti berlangganan dari Router Observables. Dia juga menyebutkan httplayanan dan ActivatedRoute.paramsdalam video ini dari November 2016 .

--- Jawaban Asli

TLDR:

Untuk pertanyaan ini ada (2) jenis Observables- nilai terbatas dan nilai tak terbatas .

http Observablesmenghasilkan nilai hingga (1) dan sesuatu seperti DOM event listener Observablesmenghasilkan nilai tak hingga .

Jika Anda memanggil secara manual subscribe(tidak menggunakan pipa async), maka unsubscribedari infinite Observables .

Jangan khawatir tentang yang terbatas , RxJsakan mengurusnya.

Sumber 1

Saya melacak jawaban dari Rob Wormald di Angular's Gitter di sini .

Dia menyatakan (saya mengatur ulang untuk kejelasan dan penekanan adalah milikku)

jika ini adalah single-value-sequence (seperti permintaan http) pembersihan manual tidak diperlukan (dengan asumsi Anda berlangganan controller secara manual)

saya harus mengatakan "jika itu urutan yang melengkapi " (yang urutan nilai tunggal, ala http, adalah satu)

jika urutannya tidak terbatas , Anda harus berhenti berlangganan yang mana dilakukan oleh pipa async untuk Anda

Dia juga menyebutkan dalam video youtube ini di Observables that they clean up after themselves... dalam konteks Observables yang complete(seperti Janji, yang selalu lengkap karena mereka selalu menghasilkan 1 nilai dan berakhir - kami tidak pernah khawatir tentang berhenti berlangganan dari Janji untuk memastikan mereka membersihkan xhracara pendengar, kan?).

Sumber 2

Juga di panduan Rangle ke Angular 2 berbunyi

Dalam kebanyakan kasus, kita tidak perlu secara eksplisit memanggil metode berhenti berlangganan kecuali jika kita ingin membatalkan lebih awal atau Observable kita memiliki umur yang lebih panjang daripada berlangganan kita. Perilaku default dari operator yang dapat diobservasi adalah membuang langganan begitu pesan .complete () atau .error () dipublikasikan. Perlu diingat bahwa RxJS dirancang untuk digunakan dalam mode "api dan lupakan" sebagian besar waktu.

Kapan frasa ini our Observable has a longer lifespan than our subscriptionberlaku?

Ini berlaku ketika langganan dibuat di dalam komponen yang dihancurkan sebelum (atau tidak 'lama' sebelum) Observableselesai.

Saya membaca ini sebagai makna jika kita berlangganan httppermintaan atau yang dapat diamati yang memancarkan 10 nilai dan komponen kita dihancurkan sebelum httppermintaan itu kembali atau 10 nilai telah dipancarkan, kita masih ok!

Ketika permintaan kembali atau nilai ke-10 akhirnya dikeluarkan Observableakan menyelesaikan dan semua sumber daya akan dibersihkan.

Sumber 3

Jika kita melihat contoh ini dari panduan Rangle yang sama, kita dapat melihat bahwa Subscriptionto route.paramstidak memerlukan unsubscribe()karena kita tidak tahu kapan mereka paramsakan berhenti berubah (mengeluarkan nilai baru).

Komponen dapat dihancurkan dengan menavigasi pergi dalam hal ini params rute kemungkinan masih akan berubah (mereka secara teknis bisa berubah sampai aplikasi berakhir) dan sumber daya yang dialokasikan dalam berlangganan masih akan dialokasikan karena belum ada completion.

seangwright
sumber
16
Memanggil complete()dengan sendirinya tampaknya tidak membersihkan langganan. Bagaimanapun panggilan next()dan kemudian complete(), saya percaya takeUntil()hanya berhenti ketika nilai diproduksi, bukan ketika urutan berakhir.
Firefly
3
@ seangwright Tes cepat dengan anggota tipe Subjectdi dalam komponen dan beralih dengan ngIfuntuk memicu ngOnInitdan ngOnDestroymenunjukkan, bahwa subjek dan langganannya tidak akan pernah lengkap atau dibuang (terhubung dengan finallyoperator ke langganan). Saya harus menelepon Subject.complete()di ngOnDestroy, sehingga langganan dapat membersihkan diri mereka sendiri.
Lars
4
Anda --- Edit 3 adalah sangat mendalam, terima kasih! Saya hanya punya pertanyaan tindak lanjut: jika menggunakan takeUnitlpendekatan ini, kami tidak pernah harus berhenti berlangganan secara manual dari yang terlihat? Apakah itu masalahnya? Selain itu, mengapa kita perlu panggilan next()di ngOnDestroy, mengapa tidak hanya panggilan complete()?
kode jelek
7
@ Seangwright Itu mengecewakan; pelat tambahan itu mengganggu.
spongessuck
4
Sunting 3 yang dibahas dalam konteks acara di medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
HankCa
91

Anda tidak perlu memiliki banyak langganan dan berhenti berlangganan secara manual. Gunakan Subjek dan ambil Kombinasikan hingga untuk menangani langganan seperti bos:

import { Subject } from "rxjs"
import { takeUntil } from "rxjs/operators"

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  componentDestroyed$: Subject<boolean> = new Subject()

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something 2 */ })

    //...

    this.titleService.emitterN$
      .pipe(takeUntil(this.componentDestroyed$))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  ngOnDestroy() {
    this.componentDestroyed$.next(true)
    this.componentDestroyed$.complete()
  }
}

Pendekatan alternatif , yang diusulkan oleh @acumartini dalam komentar , menggunakan takeWhile alih-alih takeUntil . Anda mungkin lebih menyukainya, tetapi ingatlah bahwa cara ini eksekusi Anda yang Teramati tidak akan dibatalkan pada ngDestroy komponen Anda (misalnya ketika Anda membuat perhitungan yang memakan waktu atau menunggu data dari server). Metode, yang didasarkan pada takeUntil , tidak memiliki kelemahan ini dan menyebabkan pembatalan permintaan segera. Terima kasih kepada @AlexChe untuk penjelasan terperinci dalam komentar .

Jadi inilah kodenya:

@Component({
  moduleId: __moduleName,
  selector: "my-view",
  templateUrl: "../views/view-route.view.html"
})
export class ViewRouteComponent implements OnInit, OnDestroy {
  alive: boolean = true

  constructor(private titleService: TitleService) {}

  ngOnInit() {
    this.titleService.emitter1$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 1 */ })

    this.titleService.emitter2$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something 2 */ })

    // ...

    this.titleService.emitterN$
      .pipe(takeWhile(() => this.alive))
      .subscribe((data: any) => { /* ... do something N */ })
  }

  // Probably, this.alive = false MAY not be required here, because
  // if this.alive === undefined, takeWhile will stop. I
  // will check it as soon, as I have time.
  ngOnDestroy() {
    this.alive = false
  }
}
pembuat metamaker
sumber
2
Jika dia hanya menggunakan bool untuk menjaga negara, bagaimana membuat "takeUntil" berfungsi seperti yang diharapkan?
Val
6
Saya pikir ada perbedaan yang signifikan antara menggunakan takeUntildan takeWhile. Mantan berhenti berlangganan dari sumber diamati segera ketika dipecat, sedangkan yang terakhir berhenti berlangganan segera setelah nilai berikutnya dihasilkan oleh sumber diamati. Jika menghasilkan nilai oleh sumber yang dapat diamati adalah operasi yang memakan sumber daya, memilih di antara keduanya dapat melampaui preferensi gaya. Lihat bungkusan itu
Alex Che
2
@AlexChe terima kasih telah memberikan bungkusan yang menarik! Ini adalah poin yang sangat valid untuk penggunaan umum takeUntilvs takeWhile, bukan untuk kasus khusus kami. Ketika kita perlu berhenti berlangganan pendengar pada penghancuran komponen , kita hanya memeriksa nilai boolean seperti () => alivedalam takeWhile, sehingga setiap operasi memakan waktu / memori tidak digunakan dan perbedaannya cukup banyak tentang styling (ofc, untuk kasus khusus ini).
metamaker
2
@metamaker Say, dalam komponen kami, kami berlangganan sebuah Observable, yang secara internal menambang beberapa mata uang kripto dan membuat nextacara untuk setiap koin yang ditambang, dan menambang satu koin semacam itu membutuhkan waktu sehari. Dengan takeUntilkami akan berhenti berlangganan dari penambangan sumber Observablesegera setelah ngOnDestroydipanggil selama penghancuran komponen kami. Dengan demikian Observablefungsi penambangan dapat membatalkan operasinya segera selama proses ini.
Alex Che
2
OTOH, jika kita gunakan takeWhile, di ngOnDestorykita baru mengatur variabel boolean. Tetapi Observablefungsi penambangan mungkin masih berfungsi hingga satu hari, dan hanya selama nextpanggilan itu akan menyadari bahwa tidak ada langganan aktif dan perlu dibatalkan.
Alex Che
76

Kelas Berlangganan memiliki fitur menarik:

Merupakan sumber daya sekali pakai, seperti pelaksanaan Observable. Langganan memiliki satu metode penting, berhenti berlangganan, yang tidak memerlukan argumen dan hanya membuang sumber daya yang dimiliki oleh langganan.
Selain itu, langganan dapat dikelompokkan bersama melalui metode add (), yang akan melampirkan Langganan anak ke Langganan saat ini. Ketika Langganan berhenti berlangganan, semua anak-anaknya (dan cucu-cucunya) juga akan berhenti berlangganan.

Anda dapat membuat objek Berlangganan agregat yang mengelompokkan semua langganan Anda. Anda melakukan ini dengan membuat Langganan kosong dan menambahkan langganan ke dalamnya menggunakan add()metodenya. Ketika komponen Anda hancur, Anda hanya perlu berhenti berlangganan langganan agregat.

@Component({ ... })
export class SmartComponent implements OnInit, OnDestroy {
  private subscriptions = new Subscription();

  constructor(private heroService: HeroService) {
  }

  ngOnInit() {
    this.subscriptions.add(this.heroService.getHeroes().subscribe(heroes => this.heroes = heroes));
    this.subscriptions.add(/* another subscription */);
    this.subscriptions.add(/* and another subscription */);
    this.subscriptions.add(/* and so on */);
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
Steven Liekens
sumber
1
Saya menggunakan pendekatan ini. Ingin tahu apakah ini lebih baik daripada menggunakan pendekatan dengan takeUntil (), seperti dalam jawaban yang diterima .. kekurangannya?
Manuel Di Iorio
1
Tidak ada kekurangan yang saya sadari. Saya tidak berpikir ini lebih baik, hanya berbeda.
Steven Liekens
3
Lihat medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87 untuk diskusi lebih lanjut tentang takeUntilpendekatan resmi versus pendekatan pengumpulan langganan dan panggilan ini unsubscribe. (Pendekatan ini tampaknya jauh lebih bersih bagi saya.)
Josh Kelley
4
Satu manfaat kecil dari jawaban ini: Anda tidak perlu memeriksa apakah this.subscriptionsitu nol
user2023861
2
Cukup hindari perangkaian metode tambahkan seperti sub = subsciption.add(..).add(..)karena dalam banyak kasus ini menghasilkan hasil yang tidak terduga github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov
32

Beberapa praktik terbaik terkait berhenti berlangganan yang dapat diamati di dalam komponen Angular:

Kutipan dari Routing & Navigation

Saat berlangganan suatu yang dapat diamati dalam suatu komponen, Anda hampir selalu mengatur untuk berhenti berlangganan ketika komponen tersebut dihancurkan.

Ada beberapa yang dapat diamati luar biasa di mana ini tidak perlu. ObservableRoute yang dapat diamati adalah beberapa pengecualian.

ActivatedRoute dan yang dapat diobservasi terisolasi dari Router itu sendiri. Router menghancurkan komponen yang dirutekan ketika tidak lagi diperlukan dan ActivatedRoute yang disuntikkan mati bersamanya.

Tetaplah untuk berhenti berlangganan. Ini tidak berbahaya dan tidak pernah merupakan praktik yang buruk.

Dan dalam menanggapi tautan berikut:

Saya mengumpulkan beberapa praktik terbaik terkait berhenti berlangganan yang dapat diamati di dalam komponen Angular untuk dibagikan kepada Anda:

  • httpberhenti berlangganan yang dapat diamati adalah bersyarat dan kami harus mempertimbangkan efek dari 'berlangganan panggilan balik' dijalankan setelah komponen dihancurkan berdasarkan kasus per kasus. Kita tahu bahwa sudut berhenti berlangganan dan membersihkan yang httpdapat diamati itu sendiri (1) , (2) . Meskipun ini benar dari perspektif sumber daya, itu hanya menceritakan setengah dari cerita. Katakanlah kita berbicara tentang panggilan langsung httpdari dalam suatu komponen, dan httpresponsnya memakan waktu lebih lama dari yang dibutuhkan sehingga pengguna menutup komponen tersebut. Itusubscribe()handler akan tetap dipanggil walaupun komponennya ditutup dan dihancurkan. Ini dapat memiliki efek samping yang tidak diinginkan dan dalam skenario yang lebih buruk membuat keadaan aplikasi rusak. Ini juga dapat menyebabkan pengecualian jika kode dalam panggilan balik mencoba memanggil sesuatu yang baru saja dibuang. Namun pada saat yang sama sesekali mereka diinginkan. Seperti, katakanlah Anda sedang membuat klien email dan Anda memicu suara ketika email selesai dikirim - yah Anda tetap ingin itu terjadi bahkan jika komponen ditutup ( 8 ).
  • Tidak perlu berhenti berlangganan dari observable yang lengkap atau kesalahan. Namun, tidak ada salahnya melakukannya (7) .
  • Gunakan AsyncPipesebanyak mungkin karena secara otomatis berhenti berlangganan dari yang dapat diamati pada kerusakan komponen.
  • Berhenti berlangganan dari ActivatedRouteobservable seperti route.paramsjika mereka berlangganan di dalam bersarang (Ditambahkan dalam tpl dengan pemilih komponen) atau komponen dinamis karena mereka dapat berlangganan berkali-kali selama komponen induk / host ada. Tidak perlu berhenti berlangganan dari mereka dalam skenario lain seperti yang disebutkan dalam kutipan di atas dari Routing & Navigationdokumen.
  • Berhenti berlangganan dari global yang dapat diamati dibagi antara komponen yang diekspos melalui layanan Angular misalnya karena mereka dapat berlangganan beberapa kali selama komponen diinisialisasi.
  • Tidak perlu berhenti berlangganan dari internal yang dapat diamati layanan aplikasi karena layanan ini tidak pernah hancur, kecuali seluruh aplikasi Anda hancur, tidak ada alasan nyata untuk berhenti berlangganan dan tidak ada kemungkinan kebocoran memori. (6) .

    Catatan: Mengenai layanan mencakup, yaitu penyedia komponen, mereka hancur ketika komponen dihancurkan. Dalam hal ini, jika kami berlangganan setiap yang dapat diamati di dalam penyedia ini, kami harus mempertimbangkan untuk berhenti berlangganan menggunakan OnDestroykait siklus hidup yang akan dipanggil saat layanan dihancurkan, menurut dokumen.
  • Gunakan teknik abstrak untuk menghindari kekacauan kode yang mungkin dihasilkan dari berhenti berlangganan. Anda dapat mengelola langganan Anda dengan takeUntil (3) atau Anda dapat menggunakan npm paket ini di (4) Cara termudah untuk berhenti berlangganan dari Observables in Angular .
  • Selalu berhenti berlangganan dari yang bisa FormGroupdiamati seperti form.valueChangesdanform.statusChanges
  • Selalu berhenti berlangganan dari layanan yang dapat diamati Renderer2sepertirenderer2.listen
  • Berhenti berlangganan dari setiap hal lain yang dapat diamati sebagai langkah pelindung kebocoran memori hingga Angular Docs secara eksplisit memberi tahu kita mana yang dapat diobservasi yang tidak perlu dibatalkan berlangganan (Lihat masalah: (5) Dokumentasi untuk Berhenti Berlangganan RxJS (Buka) ).
  • Bonus: Selalu gunakan cara Angular untuk mengikat acara seperti HostListenerkarena sudut peduli dengan baik tentang menghapus pendengar acara jika diperlukan dan mencegah kebocoran memori potensial karena ikatan acara.

Kiat terakhir yang bagus : Jika Anda tidak tahu apakah ada pengamat yang secara otomatis berhenti berlangganan / selesai atau tidak, tambahkan completepanggilan balik ke subscribe(...)dan periksa apakah itu dipanggil saat komponen dihancurkan.

Mouneer
sumber
Jawaban untuk No. 6 tidak tepat. Layanan dihancurkan dan ngOnDestroydisebut ketika layanan diberikan pada tingkat selain tingkat root misalnya disediakan secara eksplisit dalam komponen yang kemudian dihapus. Dalam kasus ini, Anda harus berhenti berlangganan dari layanan yang dapat diamati
Drenai
@Renai, terima kasih atas komentar Anda dan dengan sopan saya tidak setuju. Jika komponen dihancurkan, komponen, layanan, dan yang dapat diobservasi akan semuanya GCed dan berhenti berlangganan akan sia-sia dalam kasus ini kecuali Anda menyimpan referensi untuk yang dapat diamati di mana saja dari komponen (Yang tidak logis untuk membocorkan status komponen secara global meskipun cakupan layanan ke komponen)
Mouneer
Jika layanan yang dimusnahkan memiliki langganan ke layanan yang dapat diamati milik layanan lain yang lebih tinggi dalam hierarki DI, maka GC tidak akan terjadi. Menghindari skenario ini dengan di berhenti berlangganan ngOnDestroy, yang selalu disebut ketika layanan dihancurkan github.com/angular/angular/commit/...
Drenai
1
@ Donai, Periksa jawaban yang diperbarui.
Mouneer
3
@Tim Pertama-tama, Feel free to unsubscribe anyway. It is harmless and never a bad practice.dan mengenai pertanyaan Anda, itu tergantung. Jika komponen anak diinisiasi beberapa kali (Misalnya, ditambahkan di dalam ngIfatau dimuat secara dinamis), Anda harus berhenti berlangganan untuk menghindari menambahkan beberapa langganan ke pengamat yang sama. Kalau tidak, tidak perlu. Tapi saya lebih suka berhenti berlangganan di dalam komponen anak karena ini membuatnya lebih dapat digunakan kembali dan terisolasi dari cara itu dapat digunakan.
Mouneer
18

Tergantung. Jika dengan menelepon someObservable.subscribe(), Anda mulai menahan beberapa sumber daya yang harus dibebaskan secara manual ketika siklus hidup komponen Anda berakhir, maka Anda harus menelepon theSubscription.unsubscribe()untuk mencegah kebocoran memori.

Mari kita lihat lebih dekat contoh Anda:

getHero()mengembalikan hasil http.get(). Jika Anda melihat kode sumber sudut 2 , http.get()buat dua pendengar acara:

_xhr.addEventListener('load', onLoad);
_xhr.addEventListener('error', onError);

dan dengan menelepon unsubscribe(), Anda dapat membatalkan permintaan serta pendengar:

_xhr.removeEventListener('load', onLoad);
_xhr.removeEventListener('error', onError);
_xhr.abort();

Perhatikan bahwa _xhrini spesifik untuk platform tetapi saya pikir aman untuk menganggap bahwa ini adalah XMLHttpRequest()untuk Anda.

Biasanya, ini cukup bukti untuk menjamin unsubscribe()panggilan manual . Namun menurut spesifikasi WHATWG ini , XMLHttpRequest()pengumpulan sampah dapat dilakukan setelah selesai, bahkan jika ada pendengar acara yang terikat padanya. Jadi saya kira itu sebabnya panduan resmi sudut 2 tidak ada unsubscribe()dan memungkinkan GC membersihkan pendengar.

Adapun contoh kedua Anda, itu tergantung pada implementasi params. Sampai hari ini, panduan resmi sudut tidak lagi menunjukkan berhenti berlangganan params. Saya melihat ke src lagi dan menemukan itu paramshanya sebuah BehaviorSubject . Karena tidak ada pendengar acara atau timer digunakan, dan tidak ada variabel global yang dibuat, itu harus aman untuk dihilangkan unsubscribe().

Intinya untuk pertanyaan Anda adalah bahwa selalu memanggil unsubscribe()sebagai pelindung terhadap kebocoran memori, kecuali jika Anda yakin bahwa eksekusi yang diamati tidak menciptakan variabel global, menambahkan pendengar acara, mengatur penghitung waktu, atau melakukan hal lain yang menyebabkan kebocoran memori .

Jika ragu, lihat implementasi yang dapat diamati. Jika observable telah menulis beberapa logika pembersihan ke dalamnya unsubscribe(), yang biasanya merupakan fungsi yang dikembalikan oleh konstruktor, maka Anda memiliki alasan yang baik untuk secara serius mempertimbangkan untuk memanggil unsubscribe().

Chuanqi Sun
sumber
6

Dokumentasi resmi Angular 2 memberikan penjelasan kapan harus berhenti berlangganan dan kapan bisa diabaikan dengan aman. Lihat tautan ini:

https://angular.io/docs/ts/latest/cookbook/component-communication.html#!#bidirectional-service

Cari paragraf dengan tajuk Orang Tua dan anak-anak berkomunikasi melalui layanan dan kemudian kotak biru:

Perhatikan bahwa kami menangkap langganan dan berhenti berlangganan ketika AstronautComponent dihancurkan. Ini adalah langkah pelindung kebocoran memori. Tidak ada risiko aktual dalam aplikasi ini karena masa hidup AstronautComponent sama dengan masa pakai aplikasi itu sendiri. Itu tidak selalu benar dalam aplikasi yang lebih kompleks.

Kami tidak menambahkan penjaga ini ke MissionControlComponent karena, sebagai induknya, ia mengontrol masa pakai MissionService.

Saya harap ini membantu Anda.

Cerny
sumber
3
sebagai komponen Anda tidak pernah tahu apakah Anda seorang anak atau tidak. oleh karena itu Anda harus selalu berhenti berlangganan dari langganan sebagai praktik terbaik.
SeriousM
1
Poin tentang MissionControlComponent tidak benar-benar tentang apakah itu orang tua atau bukan, itu adalah komponen itu sendiri yang menyediakan layanan. Ketika MissionControl hancur, begitu juga layanan dan referensi ke instance dari layanan, dengan demikian tidak ada kemungkinan kebocoran.
ender
6

Berdasarkan: Menggunakan pewarisan Kelas untuk menghubungkan ke siklus hidup komponen 2 Sudut

Pendekatan generik lain:

export abstract class UnsubscribeOnDestroy implements OnDestroy {
  protected d$: Subject<any>;

  constructor() {
    this.d$ = new Subject<void>();

    const f = this.ngOnDestroy;
    this.ngOnDestroy = () => {
      f();
      this.d$.next();
      this.d$.complete();
    };
  }

  public ngOnDestroy() {
    // no-op
  }

}

Dan gunakan:

@Component({
    selector: 'my-comp',
    template: ``
})
export class RsvpFormSaveComponent extends UnsubscribeOnDestroy implements OnInit {

    constructor() {
        super();
    }

    ngOnInit(): void {
      Observable.of('bla')
      .takeUntil(this.d$)
      .subscribe(val => console.log(val));
    }
}

JoG
sumber
1
Ini TIDAK berfungsi dengan benar. Harap berhati-hati saat menggunakan solusi ini. Anda kehilangan this.componentDestroyed$.next()panggilan seperti solusi yang diterima oleh sean di atas ...
philn
4

Jawaban Edit # 3 resmi (dan variasi) bekerja dengan baik, tetapi hal yang membuat saya adalah 'berlumpur' dari logika bisnis di sekitar langganan yang dapat diamati.

Berikut pendekatan lain menggunakan pembungkus.

Warining: kode eksperimental

File subscribeAndGuard.ts digunakan untuk membuat ekstensi yang bisa diamati baru untuk membungkus .subscribe()dan di dalamnya untuk membungkus ngOnDestroy().
Penggunaannya sama dengan .subscribe(), kecuali untuk parameter pertama tambahan yang mereferensikan komponen.

import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';

const subscribeAndGuard = function(component, fnData, fnError = null, fnComplete = null) {

  // Define the subscription
  const sub: Subscription = this.subscribe(fnData, fnError, fnComplete);

  // Wrap component's onDestroy
  if (!component.ngOnDestroy) {
    throw new Error('To use subscribeAndGuard, the component must implement ngOnDestroy');
  }
  const saved_OnDestroy = component.ngOnDestroy;
  component.ngOnDestroy = () => {
    console.log('subscribeAndGuard.onDestroy');
    sub.unsubscribe();
    // Note: need to put original back in place
    // otherwise 'this' is undefined in component.ngOnDestroy
    component.ngOnDestroy = saved_OnDestroy;
    component.ngOnDestroy();

  };

  return sub;
};

// Create an Observable extension
Observable.prototype.subscribeAndGuard = subscribeAndGuard;

// Ref: https://www.typescriptlang.org/docs/handbook/declaration-merging.html
declare module 'rxjs/Observable' {
  interface Observable<T> {
    subscribeAndGuard: typeof subscribeAndGuard;
  }
}

Berikut adalah komponen dengan dua langganan, satu dengan pembungkus dan satu tanpa. Satu-satunya peringatan adalah ia harus mengimplementasikan OnDestroy (dengan tubuh kosong jika diinginkan), jika tidak, Angular tidak tahu untuk memanggil versi yang dibungkus.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
import './subscribeAndGuard';

@Component({
  selector: 'app-subscribing',
  template: '<h3>Subscribing component is active</h3>',
})
export class SubscribingComponent implements OnInit, OnDestroy {

  ngOnInit() {

    // This subscription will be terminated after onDestroy
    Observable.interval(1000)
      .subscribeAndGuard(this,
        (data) => { console.log('Guarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );

    // This subscription will continue after onDestroy
    Observable.interval(1000)
      .subscribe(
        (data) => { console.log('Unguarded:', data); },
        (error) => { },
        (/*completed*/) => { }
      );
  }

  ngOnDestroy() {
    console.log('SubscribingComponent.OnDestroy');
  }
}

Demo plunker ada di sini

Catatan tambahan: Re Edit 3 - Solusi 'Resmi', ini dapat disederhanakan dengan menggunakan takeWhile () alih-alih takeUntil () sebelum berlangganan, dan boolean sederhana dan bukan yang lain yang bisa diamati di ngOnDestroy.

@Component({...})
export class SubscribingComponent implements OnInit, OnDestroy {

  iAmAlive = true;
  ngOnInit() {

    Observable.interval(1000)
      .takeWhile(() => { return this.iAmAlive; })
      .subscribe((data) => { console.log(data); });
  }

  ngOnDestroy() {
    this.iAmAlive = false;
  }
}
Richard Matsen
sumber
3

Karena solusi seangwright (Edit 3) tampaknya sangat berguna, saya juga merasa kesulitan untuk mengemas fitur ini ke dalam komponen dasar, dan mengisyaratkan rekan tim proyek lain untuk mengingat untuk memanggil super () di ngOnDestroy untuk mengaktifkan fitur ini.

Jawaban ini memberikan cara untuk membebaskan dari panggilan super, dan menjadikan "componentDestroyed $" sebagai inti dari komponen dasar.

class BaseClass {
    protected componentDestroyed$: Subject<void> = new Subject<void>();
    constructor() {

        /// wrap the ngOnDestroy to be an Observable. and set free from calling super() on ngOnDestroy.
        let _$ = this.ngOnDestroy;
        this.ngOnDestroy = () => {
            this.componentDestroyed$.next();
            this.componentDestroyed$.complete();
            _$();
        }
    }

    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}

Dan kemudian Anda dapat menggunakan fitur ini secara bebas misalnya:

@Component({
    selector: 'my-thing',
    templateUrl: './my-thing.component.html'
})
export class MyThingComponent extends BaseClass implements OnInit, OnDestroy {
    constructor(
        private myThingService: MyThingService,
    ) { super(); }

    ngOnInit() {
        this.myThingService.getThings()
            .takeUntil(this.componentDestroyed$)
            .subscribe(things => console.log(things));
    }

    /// optional. not a requirement to implement OnDestroy
    ngOnDestroy() {
        console.log('everything works as intended with or without super call');
    }

}
Val
sumber
3

Mengikuti jawaban oleh @seangwright , saya telah menulis kelas abstrak yang menangani langganan yang dapat diamati "dalam komponen:

import { OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs/Subscription';
import { Subject } from 'rxjs/Subject';
import { Observable } from 'rxjs/Observable';
import { PartialObserver } from 'rxjs/Observer';

export abstract class InfiniteSubscriberComponent implements OnDestroy {
  private onDestroySource: Subject<any> = new Subject();

  constructor() {}

  subscribe(observable: Observable<any>): Subscription;

  subscribe(
    observable: Observable<any>,
    observer: PartialObserver<any>
  ): Subscription;

  subscribe(
    observable: Observable<any>,
    next?: (value: any) => void,
    error?: (error: any) => void,
    complete?: () => void
  ): Subscription;

  subscribe(observable: Observable<any>, ...subscribeArgs): Subscription {
    return observable
      .takeUntil(this.onDestroySource)
      .subscribe(...subscribeArgs);
  }

  ngOnDestroy() {
    this.onDestroySource.next();
    this.onDestroySource.complete();
  }
}

Untuk menggunakannya, cukup rentangkan di komponen sudut Anda dan panggil subscribe()metode berikut:

this.subscribe(someObservable, data => doSomething());

Itu juga menerima kesalahan dan menyelesaikan panggilan balik seperti biasa, objek pengamat, atau tidak panggilan balik sama sekali. Ingatlah untuk menelepon super.ngOnDestroy()jika Anda juga menerapkan metode itu dalam komponen anak.

Temukan di sini referensi tambahan oleh Ben Lesh: RxJS: Jangan Berhenti Berlangganan .

Mau Muñoz
sumber
2

Saya mencoba solusi seangwright (Edit 3)

Itu tidak berfungsi untuk Observable yang dibuat oleh timer atau interval.

Namun, saya membuatnya bekerja dengan menggunakan pendekatan lain:

import { Component, OnDestroy, OnInit } from '@angular/core';
import 'rxjs/add/operator/takeUntil';
import { Subject } from 'rxjs/Subject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/Rx';

import { MyThingService } from '../my-thing.service';

@Component({
   selector: 'my-thing',
   templateUrl: './my-thing.component.html'
})
export class MyThingComponent implements OnDestroy, OnInit {
   private subscriptions: Array<Subscription> = [];

  constructor(
     private myThingService: MyThingService,
   ) { }

  ngOnInit() {
    const newSubs = this.myThingService.getThings()
        .subscribe(things => console.log(things));
    this.subscriptions.push(newSubs);
  }

  ngOnDestroy() {
    for (const subs of this.subscriptions) {
      subs.unsubscribe();
   }
 }
}
Jeff Tham
sumber
2

Aku seperti dua jawaban terakhir, tapi saya mengalami masalah jika subclass direferensikan "this"dingOnDestroy .

Saya memodifikasinya menjadi ini, dan sepertinya itu menyelesaikan masalah itu.

export abstract class BaseComponent implements OnDestroy {
    protected componentDestroyed$: Subject<boolean>;
    constructor() {
        this.componentDestroyed$ = new Subject<boolean>();
        let f = this.ngOnDestroy;
        this.ngOnDestroy = function()  {
            // without this I was getting an error if the subclass had
            // this.blah() in ngOnDestroy
            f.bind(this)();
            this.componentDestroyed$.next(true);
            this.componentDestroyed$.complete();
        };
    }
    /// placeholder of ngOnDestroy. no need to do super() call of extended class.
    ngOnDestroy() {}
}
Scott Williams
sumber
Anda perlu menggunakan fungsi panah untuk mengikat 'ini':this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damsorian
2

Dalam kasus berhenti berlangganan diperlukan operator berikut untuk metode pipa yang dapat diamati dapat digunakan

import { Observable, Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { OnDestroy } from '@angular/core';

export const takeUntilDestroyed = (componentInstance: OnDestroy) => <T>(observable: Observable<T>) => {
  const subjectPropertyName = '__takeUntilDestroySubject__';
  const originalOnDestroy = componentInstance.ngOnDestroy;
  const componentSubject = componentInstance[subjectPropertyName] as Subject<any> || new Subject();

  componentInstance.ngOnDestroy = (...args) => {
    originalOnDestroy.apply(componentInstance, args);
    componentSubject.next(true);
    componentSubject.complete();
  };

  return observable.pipe(takeUntil<T>(componentSubject));
};

dapat digunakan seperti ini:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { Observable } from 'rxjs';

@Component({ template: '<div></div>' })
export class SomeComponent implements OnInit, OnDestroy {

  ngOnInit(): void {
    const observable = Observable.create(observer => {
      observer.next('Hello');
    });

    observable
      .pipe(takeUntilDestroyed(this))
      .subscribe(val => console.log(val));
  }

  ngOnDestroy(): void {
  }
}

Operator membungkus metode komponen ngOnDestroy.

Penting: operator harus menjadi yang terakhir dalam pipa yang dapat diamati.

Oleg Polezky
sumber
Ini bekerja dengan baik, namun memutakhirkan ke sudut 9 tampaknya membunuhnya. Adakah yang tahu kenapa?
ymerej
1

Anda biasanya harus berhenti berlangganan ketika komponen hancur, tetapi Angular akan menanganinya lebih dan lebih lagi seperti yang kita lakukan, misalnya dalam versi minor baru Angular4, mereka memiliki bagian ini untuk rute berhenti berlangganan:

Apakah Anda perlu berhenti berlangganan?

Seperti yang dijelaskan dalam ActivatedRoute: one-stop-shop untuk bagian informasi rute dari Routing & Navigation page, Router mengelola observable yang disediakannya dan melokalisasi langganan. Langganan dibersihkan ketika komponen dihancurkan, melindungi dari kebocoran memori, sehingga Anda tidak perlu berhenti berlangganan dari rute paramMap Observable.

Juga contoh di bawah ini adalah contoh yang baik dari Angular untuk membuat komponen dan menghancurkannya setelah, lihat bagaimana komponen mengimplementasikan OnDestroy, jika Anda membutuhkan onInit, Anda juga dapat mengimplementasikannya di komponen Anda, seperti mengimplementasikan OnInit, OnDestroy

import { Component, Input, OnDestroy } from '@angular/core';  
import { MissionService } from './mission.service';
import { Subscription }   from 'rxjs/Subscription';

@Component({
  selector: 'my-astronaut',
  template: `
    <p>
      {{astronaut}}: <strong>{{mission}}</strong>
      <button
        (click)="confirm()"
        [disabled]="!announced || confirmed">
        Confirm
      </button>
    </p>
  `
})

export class AstronautComponent implements OnDestroy {
  @Input() astronaut: string;
  mission = '<no mission announced>';
  confirmed = false;
  announced = false;
  subscription: Subscription;

  constructor(private missionService: MissionService) {
    this.subscription = missionService.missionAnnounced$.subscribe(
      mission => {
        this.mission = mission;
        this.announced = true;
        this.confirmed = false;
    });
  }

  confirm() {
    this.confirmed = true;
    this.missionService.confirmMission(this.astronaut);
  }

  ngOnDestroy() {
    // prevent memory leak when component destroyed
    this.subscription.unsubscribe();
  }
}
Alireza
sumber
3
Bingung. Apa yang kamu katakan disini Anda (dokumen / catatan Angular terkini) tampaknya mengatakan bahwa Angular mengurusnya dan kemudian mengonfirmasi bahwa berhenti berlangganan adalah pola yang baik. Terima kasih.
jamie
1

Tambahan pendek lain untuk situasi yang disebutkan di atas adalah:

  • Selalu berhenti berlangganan, ketika nilai baru dalam aliran berlangganan tidak lagi diperlukan atau tidak masalah, itu akan menghasilkan jumlah pemicu yang lebih sedikit dan peningkatan kinerja dalam beberapa kasus. Kasus seperti komponen di mana data / acara berlangganan tidak ada lagi atau berlangganan baru untuk semua aliran baru diperlukan (refresh, dll.) Adalah contoh yang baik untuk berhenti berlangganan.
Krishna Ganeriwal
sumber
0

dalam aplikasi SPA di fungsi ngOnDestroy (angular lifeCycle) Untuk setiap berlangganan Anda harus berhenti berlangganan . keuntungan => untuk mencegah negara menjadi terlalu berat.

misalnya: di component1:

import {UserService} from './user.service';

private user = {name: 'test', id: 1}

constructor(public userService: UserService) {
    this.userService.onUserChange.next(this.user);
}

dalam pelayanan:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

public onUserChange: BehaviorSubject<any> = new BehaviorSubject({});

dalam komponen2:

import {Subscription} from 'rxjs/Subscription';
import {UserService} from './user.service';

private onUserChange: Subscription;

constructor(public userService: UserService) {
    this.onUserChange = this.userService.onUserChange.subscribe(user => {
        console.log(user);
    });
}

public ngOnDestroy(): void {
    // note: Here you have to be sure to unsubscribe to the subscribe item!
    this.onUserChange.unsubscribe();
}
mojtaba ramezani
sumber
0

Untuk menangani langganan saya menggunakan kelas "Unsubscriber".

Berikut adalah Kelas Unsubscriber.

export class Unsubscriber implements OnDestroy {
  private subscriptions: Subscription[] = [];

  addSubscription(subscription: Subscription | Subscription[]) {
    if (Array.isArray(subscription)) {
      this.subscriptions.push(...subscription);
    } else {
      this.subscriptions.push(subscription);
    }
  }

  unsubscribe() {
    this.subscriptions
      .filter(subscription => subscription)
      .forEach(subscription => {
        subscription.unsubscribe();
      });
  }

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

Dan Anda dapat menggunakan kelas ini di komponen / Layanan / Efek dll.

Contoh:

class SampleComponent extends Unsubscriber {
    constructor () {
        super();
    }

    this.addSubscription(subscription);
}
Pratiyush
sumber
0

Anda dapat menggunakan Subscriptionkelas terbaru untuk berhenti berlangganan Observable dengan kode yang tidak terlalu berantakan.

Kita dapat melakukan ini dengan normal variabletetapi itu akan ada override the last subscriptiondi setiap berlangganan baru jadi hindari itu, dan pendekatan ini sangat berguna ketika Anda berurusan dengan lebih banyak jumlah Obseravables, dan jenis Obeservables seperti BehavoiurSubjectdanSubject

Berlangganan

Merupakan sumber daya sekali pakai, seperti pelaksanaan Observable. Langganan memiliki satu metode penting, berhenti berlangganan, yang tidak memerlukan argumen dan hanya membuang sumber daya yang dimiliki oleh langganan.

Anda dapat menggunakan ini dalam dua cara,

  • Anda dapat langsung mendorong langganan ke Array Berlangganan

     subscriptions:Subscription[] = [];
    
     ngOnInit(): void {
    
       this.subscription.push(this.dataService.getMessageTracker().subscribe((param: any) => {
                //...  
       }));
    
       this.subscription.push(this.dataService.getFileTracker().subscribe((param: any) => {
            //...
        }));
     }
    
     ngOnDestroy(){
        // prevent memory leak when component destroyed
        this.subscriptions.forEach(s => s.unsubscribe());
      }
    
  • menggunakan add()dariSubscription

    subscriptions = new Subscription();
    
    this.subscriptions.add(subscribeOne);
    this.subscriptions.add(subscribeTwo);
    
    ngOnDestroy() {
      this.subscriptions.unsubscribe();
    }
    

A Subscriptiondapat menampung langganan anak dan dengan aman berhenti berlangganan semuanya. Metode ini menangani kemungkinan kesalahan (mis. Jika ada langganan anak nol).

Semoga ini membantu.. :)

Ganesh045
sumber
0

Paket SubSink, solusi mudah dan konsisten untuk berhenti berlangganan

Karena tidak ada orang lain yang menyebutkannya, saya ingin merekomendasikan paket Subsink yang dibuat oleh Ward Bell: https://github.com/wardbell/subsink#readme .

Saya telah menggunakannya pada suatu proyek jika kami adalah beberapa pengembang yang menggunakannya. Sangat membantu untuk memiliki cara yang konsisten yang berfungsi dalam setiap situasi.

SnorreDan
sumber
0

Untuk yang dapat diobservasi yang selesai langsung setelah memancarkan hasil seperti AsyncSubjectatau misalnya yang dapat diobservasi dari permintaan http dan Anda tidak perlu berhenti berlangganan. Tidak ada salahnya untuk memanggil unsubscribe()mereka, tetapi jika diamati adalah closedmetode berhenti berlangganan tidak akan melakukan apa-apa :

if (this.closed) {
  return;
}

Ketika Anda memiliki umur panjang yang dapat diamati yang memancarkan beberapa nilai dari waktu ke waktu (seperti misalnya a BehaviorSubjectatau a ReplaySubject) Anda harus berhenti berlangganan untuk mencegah kebocoran memori.

Anda dapat dengan mudah membuat observable yang selesai langsung setelah memancarkan hasil dari observable yang berumur panjang menggunakan operator pipa. Dalam beberapa jawaban di sini take(1)pipa disebutkan. Tapi aku lebih suka yang first()pipa . Perbedaannya take(1)adalah:

mengirimkan suatu EmptyErrorke callback kesalahan Pengamat jika Observable selesai sebelum pemberitahuan berikutnya dikirim.

Keuntungan lain dari pipa pertama adalah Anda dapat melewati predikat yang akan membantu Anda mengembalikan nilai pertama yang memenuhi kriteria tertentu:

const predicate = (result: any) => { 
  // check value and return true if it is the result that satisfies your needs
  return true;
}
observable.pipe(first(predicate)).subscribe(observer);

Pertama akan selesai secara langsung setelah memancarkan nilai pertama (atau ketika melewati argumen fungsi, nilai pertama yang memenuhi predikat Anda) sehingga tidak perlu berhenti berlangganan.

Kadang-kadang Anda tidak yakin apakah Anda telah lama diamati atau tidak. Saya tidak mengatakan ini adalah praktik yang baik tetapi Anda kemudian dapat selalu menambahkan firstpipa hanya untuk memastikan Anda tidak perlu berhenti berlangganan secara manual. Menambahkan firstpipa tambahan pada observable yang hanya akan memancarkan satu nilai tidak ada salahnya.

Selama pembangunan Anda dapat menggunakan dengan singlepipa yang akan gagal jika sumber memancarkan diamati beberapa acara. Ini dapat membantu Anda menjelajahi jenis yang dapat diamati dan apakah perlu berhenti berlangganan atau tidak.

observable.pipe(single()).subscribe(observer);

The firstdan singletampak sangat mirip, baik pipa dapat mengambil predikat opsional tetapi perbedaan yang penting dan baik diringkas dalam stackoverflow jawaban ini di sini :

Pertama

Akan memancarkan segera setelah item pertama muncul. Akan selesai setelah itu.

Tunggal

Akan gagal jika sumber yang diamati memancarkan beberapa peristiwa.


Catatan saya mencoba seakurat dan selengkap mungkin dalam jawaban saya dengan referensi ke dokumentasi resmi, tetapi tolong komentari jika ada sesuatu yang penting hilang ...

Melayu
sumber
-1

--- Perbarui Solusi Angular 9 dan Rxjs 6

  1. Menggunakan unsubscribepada ngDestroysiklus hidup Komponen Sudut
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$.subscribe( ... );
  }

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Menggunakan takeUntildi Rxjs
class SampleComponent implements OnInit, OnDestroy {
  private unsubscribe$: new Subject<void>;
  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(takeUntil(this.unsubscribe$))
    .subscribe( ... );
  }

  ngOnDestroy() {
    this.unsubscribe$.next();
    this.unsubscribe$.complete();
  }
}
  1. untuk beberapa tindakan yang Anda panggil pada saat ngOnInititu hanya terjadi satu kali ketika komponen init.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

  ngOnInit(){
    this.subscriptions = this.sampleObservable$
    .pipe(take(1))
    .subscribe( ... );
  }
}

Kami juga punya asyncpipa. Tapi, yang ini digunakan pada template (bukan di komponen Angular).

Hoang Tran Son
sumber
Contoh pertama Anda tidak lengkap.
Paul-Sebastian Manole