
Sisi sebaliknya dari langganan yang Dapat Diobservasi
Observables memiliki metode berlangganan yang disebut dengan fungsi callback untuk mendapatkan nilai yang dikirim ( emit ) ke Observable. Dalam Angular, ini digunakan dalam komponen / arahan, dan terutama dalam modul router, NgRx dan HTTP.
Jika kami berlangganan aliran, itu akan tetap terbuka dan akan dipanggil setiap kali nilai dari bagian mana pun dari aplikasi ditransfer ke sana, sampai ditutup dengan menelepon berhenti berlangganan .
@Component({...}) export class AppComponent implements OnInit { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } }
Dalam implementasi ini, kami menggunakan interval untuk mengirim nilai setiap detik. Kami berlangganan untuk mendapatkan nilai yang dikirim, dan fungsi panggilan balik kami menulis hasilnya ke konsol browser.
Sekarang, jika AppComponent dihancurkan, misalnya, setelah keluar dari komponen atau menggunakan metode destroy () , kita masih akan melihat log konsol di browser. Ini karena meskipun AppComponent dihancurkan, berlangganan tidak dibatalkan.
Jika berlangganan tidak ditutup, fungsi panggilan balik akan terus dipanggil, yang akan menyebabkan kebocoran memori yang serius dan masalah kinerja. Untuk menghindari kebocoran, perlu "berhenti berlangganan" dari Observable setiap kali.
1. Menggunakan metode berhenti berlangganan
Setiap Langganan memiliki fungsi berhenti berlangganan () untuk membebaskan sumber daya dan membatalkan eksekusi yang dapat diobservasi. Untuk mencegah kebocoran memori, Anda harus berhenti berlangganan menggunakan metode berhenti berlangganan di Observable.
Di Angular, Anda harus berhenti berlangganan dari Observable ketika komponen dihancurkan. Untungnya, Angular memiliki kait ngOnDestroy , yang dipanggil sebelum komponen dihancurkan, yang memungkinkan pengembang memastikan bahwa memori dibersihkan, untuk menghindari pembekuan langganan, port terbuka, dan "tembakan di kaki" lainnya.
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription ngOnInit () { const observable = Rx.Observable.interval(1000); this.subscription = observable.subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription.unsubscribe() } }
Kami menambahkan ngOnDestroy ke AppComponent kami dan memanggil metode berhenti berlangganan di Observable this.scription . Ketika AppComponent dihancurkan (dengan mengklik tautan, metode destroy () , dll.), Langganan tidak akan membeku, intervalnya akan dihentikan, dan tidak akan ada lagi log konsol di browser.
Tetapi bagaimana jika kita memiliki beberapa langganan?
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); } ngOnDestroy() { this.subscription1$.unsubscribe(); this.subscription2$.unsubscribe(); } }
Ada dua langganan di AppComponent dan keduanya berhenti berlangganan di ngOnDestroy hook, mencegah kebocoran memori.
Anda juga dapat mengumpulkan semua langganan dalam larik dan berhenti berlangganan dari mereka dalam satu lingkaran:
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription1$: Subscription; subscription2$: Subscription; subscriptions: Subscription[] = []; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); this.subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); this.subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscriptions.push(this.subscription1$); this.subscriptions.push(this.subscription2$); } ngOnDestroy() { this.subscriptions.forEach((subscription) => subscription.unsubscribe()); } }
Metode berlangganan mengembalikan objek RxJS jenis Berlangganan. Ini adalah sumber daya satu kali. Langganan dapat dikelompokkan menggunakan metode add , yang akan melampirkan langganan anak ke yang saat ini. Ketika langganan dibatalkan, semua anak-anaknya juga berhenti berlangganan. Mari kita coba menulis ulang AppComponent kita:
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription: Subscription; ngOnInit () { const observable1$ = Rx.Observable.interval(1000); const observable2$ = Rx.Observable.interval(400); const subscription1$ = observable.subscribe(x => console.log("From interval 1000" x)); const subscription2$ = observable.subscribe(x => console.log("From interval 400" x)); this.subscription.add(subscription1$); this.subscription.add(subscription2$); } ngOnDestroy() { this.subscription.unsubscribe() } }
Jadi kita akan menulis $ this.subscripton1 dan $ this.subscripton2 pada saat penghancuran komponen.
2. Menggunakan Async | Pipa
Pipe async berlangganan ke Observable atau Promise dan mengembalikan nilai terakhir yang dilewati. Ketika nilai baru dikirimkan, pipa async memeriksa komponen untuk pelacakan perubahan. Jika komponen hancur, pipa async secara otomatis berhenti berlangganan.
@Component({ ..., template: ` <div> Interval: {{observable$ | async}} </div> ` }) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); } }
Setelah inisialisasi, AppComponent akan membuat Observable dari metode interval. Dalam pola $ diamati , async dilewatkan. Itu berlangganan $ diamati dan menampilkan nilainya di DOM. Dia juga akan berhenti berlangganan ketika AppComponent dihancurkan. Pipa async di kelasnya berisi pengait ngOnDestroy , sehingga akan dipanggil ketika pandangannya dihancurkan.
Pipe async sangat mudah digunakan, karena dia sendiri akan berlangganan Berhenti berlangganan dan berhenti berlangganan dari mereka. Dan sekarang kita tidak bisa khawatir jika kita lupa berhenti berlangganan di ngOnDestroy .
3. Menggunakan pernyataan RxJS take *
RxJS berisi operator yang bermanfaat yang dapat Anda gunakan secara deklaratif untuk berhenti berlangganan di proyek Angular kami. Salah satunya adalah operator dari * take ** family:
- ambil (n)
- takeUntil (notifier)
- takeWhile (predikat)
ambil (n)
Operator ini memancarkan langganan asli yang ditentukan beberapa kali dan berakhir. Paling sering, satu (1) diteruskan untuk berlangganan dan keluar.
Operator ini berguna jika kita ingin Observable memberikan nilai satu kali, dan kemudian berhenti berlangganan dari stream:
@Component({...}) export class AppComponent implements OnInit { subscription$; ngOnInit () { const observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)). subscribe(x => console.log(x)); } }
berlangganan $ akan berhenti berlangganan ketika interval melewati nilai pertama.
Harap dicatat : bahkan jika AppComponent dihancurkan, berlangganan $ tidak akan membatalkan berlangganan sampai interval melewati nilai. Oleh karena itu, lebih baik memastikan bahwa semuanya berhenti berlangganan di ngOnDestroy hook:
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(take(1)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } }
takeUntil (notifier)
Pernyataan ini memancarkan nilai dari Observable asli sampai pemberi notifikasi mengirim pesan penyelesaian.
@Component({…}) export class AppComponent implements OnInit, OnDestroy { notifier = new Subject(); ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeUntil(this.notifier)).subscribe(x => console.log(x)); } ngOnDestroy() { this.notifier.next(); this.notifier.complete(); } }
Kami memiliki Subjek tambahan untuk pemberitahuan, yang akan mengirim perintah untuk berhenti berlangganan this.scription . Kami dapat diobservasi dalam pipa sampai kami ditandatangani. TakeUntil akan memancarkan pesan interval sampai pemberi notifikasi berhenti berlangganan $ . Yang terbaik adalah meletakkan notifier di kait ngOnDestroy .
takeWhile (predikat)
Pernyataan ini akan memancarkan nilai yang dapat diobservasi selama mereka cocok dengan kondisi predikat.
@Component({...}) export class AppComponent implements OnInit { ngOnInit () { const observable$ = Rx.Observable.interval(1000); observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } }
Kami memilah $ yang dapat diamati dengan operator takeWhile, yang akan mengirimkan nilai selama mereka kurang dari 10. Jika nilai lebih besar dari atau sama dengan 10 tiba, operator akan berhenti berlangganan.
Penting untuk dipahami bahwa $ berlangganan yang dapat diamati akan terbuka sampai interval memberikan 10. Oleh karena itu, untuk keamanan, kami menambahkan kait ngOnDestroy untuk berhenti berlangganan dari $ yang dapat diamati ketika komponen dihancurkan.
@Component({…}) export class AppComponent implements OnInit, OnDestroy { subscription$; ngOnInit () { var observable$ = Rx.Observable.interval(1000); this.subscription$ = observable$.pipe(takeWhile(value => value < 10)).subscribe(x => console.log(x)); } ngOnDestroy() { this.subscription$.unsubscribe(); } }
4. Menggunakan operator pertama RxJS
Pernyataan ini mirip dengan take gabungan (1) dan takeWhile.
Jika ini dipanggil tanpa parameter, maka nilai emit-it yang pertama adalah Observable dan berakhir. Jika itu disebut dengan fungsi predikat, maka memancarkan-itu adalah nilai pertama dari Observable asli, yang sesuai dengan kondisi fungsi predikat, dan berakhir.
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable = Rx.Observable.interval(1000); this.observable$.pipe(first()).subscribe(x => console.log(x)); } }
$ yang dapat diamati akan berakhir jika interval melewati nilai pertama. Ini berarti bahwa di konsol kita hanya akan melihat 1 pesan log.
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.pipe(first(val => val === 10)).subscribe(x => console.log(x)); } }
Di sini pertama tidak akan memancarkan nilai sampai interval melewati 10, dan kemudian menyelesaikan $ dapat diamati . Di konsol kita hanya akan melihat satu pesan.
Pada contoh pertama, jika AppComponent dihancurkan sebelum yang pertama mendapatkan nilai dari $ yang dapat diamati , langganan akan tetap terbuka sampai pesan pertama diterima.
Juga, dalam contoh kedua, jika AppComponent dihancurkan sebelum interval mengembalikan nilai yang cocok dengan kondisi operator, langganan akan tetap terbuka sampai interval mengembalikan 10. Oleh karena itu, untuk memastikan keamanan, kita harus secara eksplisit membatalkan langganan di kait ngOnDestroy .
5. Menggunakan Dekorator untuk mengotomatiskan berhenti berlangganan
Kita semua manusia, kita cenderung lupa. Sebagian besar metode sebelumnya mengandalkan kait ngOnDestroy untuk memastikan langganan dihapus sebelum menghancurkan komponen. Tapi kita bisa lupa mendaftarkannya di ngOnDestroy - mungkin karena tenggat waktu, atau klien gugup yang tahu di mana Anda tinggal ...
Dalam hal ini, kami dapat menggunakan Dekorator dalam proyek Angular kami untuk berhenti berlangganan secara otomatis dari semua langganan dalam komponen.
Berikut adalah contoh implementasi yang sangat berguna:
function AutoUnsub() { return function(constructor) { const orig = constructor.prototype.ngOnDestroy; constructor.prototype.ngOnDestroy = function() { for(let prop in this) { const property = this[prop]; if(typeof property.subscribe === "function") { property.unsubscribe(); } } orig.apply(); } } }
AutoUnsub ini adalah dekorator yang dapat diterapkan ke kelas di proyek Angular kami. Seperti yang Anda lihat, ini menyimpan pengait ngOnDestroy yang asli, kemudian membuat yang baru dan menghubungkannya ke kelas yang diterapkan. Dengan begitu, ketika kelas dihancurkan, sebuah kait baru dipanggil. Fungsinya melihat melalui properti kelas, dan jika menemukan Observable, maka berhenti berlangganan darinya. Kemudian ia memanggil pengait ngOnDestroy asli di kelas, jika ada.
@Component({...}) @AutoUnsub export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)) } }
Kami menerapkannya pada AppComponent kami dan tidak lagi khawatir tentang lupa untuk berhenti berlangganan dari $ observasi di ngOnDestroy - dekorator akan melakukannya untuk kami.
Tetapi metode ini juga memiliki kelemahan - kesalahan akan terjadi jika komponen kami memiliki Dapat Diobservasi tanpa berlangganan.
6. Menggunakan tslint
Terkadang pesan dari tslint dapat berguna untuk memberi tahu konsol bahwa komponen atau arahan kami belum menyatakan metode ngOnDestroy . Anda dapat menambahkan aturan khusus ke tslint untuk memperingatkan konsol ketika lint atau build sedang berjalan yang komponen kami tidak memiliki kait ngOnDestroy :
Jika kita memiliki komponen seperti itu tanpa ngOnDestroy :
@Component({...}) export class AppComponent implements OnInit { observable$; ngOnInit () { this.observable$ = Rx.Observable.interval(1000); this.observable$.subscribe(x => console.log(x)); } }
AppComponent serat akan memperingatkan kita tentang kait ngOnDestroy yang hilang:
$ ng lint Error at app.component.ts 12: Class name must have the ngOnDestroy hook
Kesimpulan
Langganan yang menggantung atau terbuka dapat menyebabkan kebocoran memori, kesalahan, perilaku yang tidak diinginkan, atau kinerja aplikasi yang buruk. Untuk menghindari ini, kami melihat berbagai cara untuk berhenti berlangganan dari Observable in proyek Angular. Dan yang mana untuk digunakan dalam situasi tertentu terserah Anda.