Angular / RxJs Khi nào tôi nên hủy đăng ký `Đăng ký`


720

Khi nào tôi nên lưu trữ các Subscriptionphiên bản và gọi unsubscribe()trong vòng đời NgOnDestroy và khi nào tôi có thể đơn giản bỏ qua chúng?

Lưu tất cả các đăng ký giới thiệu rất nhiều lộn xộn vào mã thành phần.

Hướng dẫn khách hàng HTTP bỏ qua các đăng ký như thế này:

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

Đồng thời Hướng dẫn Tuyến đường & Điều hướng nói rằng:

Cuối cùng, chúng tôi sẽ điều hướng đến một nơi khác. Bộ định tuyến sẽ loại bỏ thành phần này khỏi DOM và phá hủy nó. Chúng ta cần dọn dẹp sau khi chính mình trước khi điều đó xảy ra. Cụ thể, chúng ta phải hủy đăng ký trước khi Angular phá hủy thành phần. Không làm như vậy có thể tạo ra rò rỉ bộ nhớ.

Chúng tôi bỏ đăng ký từ chúng tôi Observabletrong ngOnDestroyphương pháp.

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();
}

20
Tôi đoán Subscriptions http-requestscó thể bị bỏ qua, vì họ chỉ gọi onNextmột lần và sau đó họ gọi onComplete. Các Routerthay vì gọi onNextnhiều lần và có thể không bao giờ gọi onComplete(không chắc chắn về điều đó ...). Tương tự với Observables từ Events. Vì vậy, tôi đoán những người nên được unsubscribed.
Springrbua

1
@ gt6707a Luồng hoàn thành (hoặc không hoàn thành) độc lập với mọi quan sát về sự hoàn thành đó. Các cuộc gọi lại (người quan sát) được cung cấp cho chức năng đăng ký không xác định nếu tài nguyên được phân bổ. Đó là lời kêu gọi subscribechính nó có khả năng phân bổ nguồn lực ngược dòng.
seangwright

Câu trả lời:


981

--- Chỉnh sửa 4 - Tài nguyên bổ sung (2018/09/01)

Trong một tập gần đây của Adventures in Angular Ben Lesh và Ward Bell thảo luận về các vấn đề xung quanh cách thức / thời điểm hủy đăng ký thành phần. Cuộc thảo luận bắt đầu vào khoảng 1:05:30.

Ward đề cập right now there's an awful takeUntil dance that takes a lot of machineryvà Shai Reznik đề cập Angular handles some of the subscriptions like http and routing.

Đáp lại, Ben đề cập rằng có các cuộc thảo luận ngay bây giờ để cho phép Observables kết nối với các sự kiện vòng đời thành phần Angular và Ward gợi ý một sự kiện quan sát trong vòng đời mà một thành phần có thể đăng ký như một cách để biết khi nào hoàn thành Observables được duy trì như trạng thái bên trong thành phần.

Điều đó nói rằng, chúng ta chủ yếu cần các giải pháp ngay bây giờ vì vậy đây là một số tài nguyên khác.

  1. Một khuyến nghị cho takeUntil()mẫu từ thành viên nhóm nòng cốt RxJs Nicholas Jamieson và một quy tắc tslint để giúp thực thi nó. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. Gói npm nhẹ hiển thị toán tử có thể quan sát được, lấy một thể hiện thành phần ( this) làm tham số và tự động hủy đăng ký trong khi ngOnDestroy. https://github.com/NetanelBasal/ngx-take-until-destroy

  3. Một biến thể khác ở trên với công thái học tốt hơn một chút nếu bạn không thực hiện các bản dựng AOT (nhưng tất cả chúng ta nên thực hiện AOT ngay bây giờ). https://github.com/smnbbrv/ngx-rx-collector

  4. Chỉ thị tùy chỉnh *ngSubscribehoạt động như ống async nhưng tạo chế độ xem được nhúng trong mẫu của bạn để bạn có thể tham khảo giá trị 'chưa được bao bọc' trong toàn bộ mẫu của mình. https://netbasal.com/diy-subcrip-handling-directive-in-angular-c8f6e762697f

Tôi đã đề cập trong một bình luận trên blog của Nicholas rằng việc sử dụng quá mức takeUntil()có thể là một dấu hiệu cho thấy thành phần của bạn đang cố gắng làm quá nhiều và nên tách các thành phần hiện có của bạn thành các thành phần Tính năngTrình bày . Sau đó, bạn có thể | asyncquan sát từ thành phần Tính năng thành một Inputthành phần Trình bày, có nghĩa là không cần đăng ký ở bất cứ đâu. Tìm hiểu thêm về phương pháp này ở đây

--- Chỉnh sửa 3 - Giải pháp 'Chính thức' (2017/04/09)

Tôi đã nói chuyện với Ward Bell về câu hỏi này tại NGConf (tôi thậm chí đã chỉ cho anh ấy câu trả lời mà anh ấy nói là đúng) nhưng anh ấy nói với tôi rằng nhóm tài liệu cho Angular có một giải pháp cho câu hỏi này chưa được công bố (mặc dù họ đang làm việc để được chấp thuận ). Ông cũng nói với tôi rằng tôi có thể cập nhật câu trả lời SO của mình với khuyến nghị chính thức sắp tới.

Giải pháp tất cả chúng ta nên sử dụng trong tương lai là thêm một private ngUnsubscribe = new Subject();trường vào tất cả các thành phần có .subscribe()lệnh gọi đến Observables trong mã lớp của chúng.

Chúng tôi sau đó gọi this.ngUnsubscribe.next(); this.ngUnsubscribe.complete();trong ngOnDestroy()phương pháp của chúng tôi .

Nước sốt bí mật (như đã được ghi nhận bởi @metamaker ) là gọi takeUntil(this.ngUnsubscribe)trước mỗi .subscribe()cuộc gọi của chúng tôi sẽ đảm bảo tất cả các đăng ký sẽ được dọn sạch khi thành phần bị phá hủy.

Thí dụ:

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();
    }
}

Lưu ý: Điều quan trọng là thêm takeUntiltoán tử là toán tử cuối cùng để tránh rò rỉ với các vật quan sát trung gian trong chuỗi toán tử.

--- Chỉnh sửa 2 (2016/12/28)

Nguồn 5

Hướng dẫn Angular, chương Routing hiện nêu như sau: "Bộ định tuyến quản lý các thiết bị quan sát mà nó cung cấp và bản địa hóa các đăng ký. Các đăng ký được dọn sạch khi thành phần bị phá hủy, bảo vệ chống rò rỉ bộ nhớ, vì vậy chúng tôi không cần hủy đăng ký các thông số tuyến đường quan sát được. " - Mark Rajcok

Dưới đây là một cuộc thảo luận về các vấn đề Github cho các tài liệu Angular liên quan đến Bộ quan sát Bộ định tuyến trong đó Ward Bell đề cập đến việc làm rõ tất cả những điều này đang được thực hiện.

--- Chỉnh sửa 1

Nguồn 4

Trong video này từ NgEurope Rob Wormald cũng nói rằng bạn không cần hủy đăng ký khỏi Bộ quan sát Bộ định tuyến. Ông cũng đề cập đến httpdịch vụ và ActivatedRoute.paramstrong video này từ tháng 11 năm 2016 .

--- Câu trả lời gốc

TLDR:

Đối với câu hỏi này, có (2) loại Observables- giá trị hữu hạn và giá trị vô hạn .

http Observablestạo ra các giá trị hữu hạn (1) và một cái gì đó giống như DOM event listener Observablestạo ra các giá trị vô hạn .

Nếu bạn gọi thủ công subscribe(không sử dụng ống async), thì unsubscribetừ vô hạn Observables .

Đừng lo lắng về những cái hữu hạn , RxJssẽ chăm sóc chúng.

Nguồn 1

Tôi đã theo dõi câu trả lời từ Rob Wormald trong Angular's Gitter tại đây .

Ông tuyên bố (tôi tổ chức lại cho rõ ràng và nhấn mạnh là của tôi)

nếu đó là một chuỗi giá trị đơn (như yêu cầu http) thì việc dọn dẹp thủ công là không cần thiết (giả sử bạn đăng ký trong bộ điều khiển theo cách thủ công)

tôi nên nói "nếu đó là một chuỗi hoàn thành " (trong đó các chuỗi giá trị đơn, a la http, là một)

nếu đó là một chuỗi vô hạn , bạn nên hủy đăng ký ống async nào cho bạn

Ngoài ra, ông còn đề cập đến video youtube này trên Đài quan sát rằng they clean up after themselves... trong bối cảnh của Đài quan sát complete(như Promise, luôn hoàn thành vì chúng luôn tạo ra 1 giá trị và kết thúc - chúng tôi không bao giờ lo lắng về việc hủy đăng ký từ Promise để đảm bảo chúng dọn sạch xhrsự kiện người nghe, phải không?).

Nguồn 2

Cũng trong hướng dẫn Rangle cho Angular 2, nó đọc

Trong hầu hết các trường hợp, chúng tôi sẽ không cần phải gọi một cách rõ ràng phương thức hủy đăng ký trừ khi chúng tôi muốn hủy sớm hoặc Observable của chúng tôi có tuổi thọ dài hơn đăng ký của chúng tôi. Hành vi mặc định của các nhà khai thác quan sát là loại bỏ đăng ký ngay khi các thông báo .complete () hoặc .error () được xuất bản. Hãy nhớ rằng RxJS được thiết kế để được sử dụng theo kiểu "lửa và quên" hầu hết thời gian.

Khi nào cụm từ our Observable has a longer lifespan than our subscriptionáp dụng?

Nó áp dụng khi đăng ký được tạo bên trong một thành phần bị hủy trước đó (hoặc không phải là "lâu" trước khi Observablehoàn thành.

Tôi đọc điều này có nghĩa là nếu chúng tôi đăng ký một httpyêu cầu hoặc có thể quan sát được phát ra 10 giá trị và thành phần của chúng tôi bị hủy trước khi httpyêu cầu đó trả về hoặc 10 giá trị đã được phát ra, chúng tôi vẫn ổn!

Khi yêu cầu trở lại hoặc giá trị thứ 10 cuối cùng được phát ra, Observablenó sẽ hoàn thành và tất cả các tài nguyên sẽ được dọn sạch.

Nguồn 3

Nếu chúng ta nhìn vào ví dụ này so với cùng Rangle hướng dẫn chúng ta có thể thấy rằng Subscriptionđể route.paramskhông đòi hỏi một unsubscribe()bởi vì chúng ta không biết khi nào những paramssẽ ngừng thay đổi (phát ra những giá trị mới).

Thành phần này có thể bị phá hủy bằng cách điều hướng đi trong trường hợp thông số tuyến có thể vẫn sẽ thay đổi (chúng có thể thay đổi về mặt kỹ thuật cho đến khi ứng dụng kết thúc) và tài nguyên được phân bổ trong đăng ký vẫn sẽ được phân bổ vì chưa có completion.


15
Gọi complete()bằng chính nó không xuất hiện để dọn sạch các mục đăng ký. Tuy nhiên, gọi next()và sau đó complete(), tôi tin rằng takeUntil()chỉ dừng lại khi một giá trị được tạo ra, không phải khi chuỗi kết thúc.
Đom đóm

2
@seangwright Một test nhanh với một thành viên của loại Subjectbên trong một thành phần và chuyển đổi qua lại nó với ngIfđể kích hoạt ngOnInitngOnDestroychương trình, đó là chủ đề và đăng ký nó sẽ không bao giờ hoàn toàn hoặc bị xử lý (nối một finally-operator để đăng ký). Tôi phải gọi Subject.complete()trong ngOnDestroy, vì vậy các mục đăng ký có thể dọn dẹp sau khi bản thân họ.
Lars

3
--- Chỉnh sửa 3 của bạn rất sâu sắc, cảm ơn! Tôi chỉ có một câu hỏi tiếp theo: nếu sử dụng takeUnitlphương pháp này, chúng ta không bao giờ phải hủy đăng ký thủ công khỏi bất kỳ vật quan sát nào? Có phải vậy không? Hơn nữa, tại sao chúng ta cần gọi next()trong ngOnDestroy, tại sao không chỉ gọi complete()?
uglycode

6
@seangwright Thật đáng thất vọng; các nồi hơi bổ sung là gây phiền nhiễu.
spongessuck

3
Chỉnh sửa 3 thảo luận trong bối cảnh của các sự kiện tại Medium.com/@ben Meat/rxjs
dont

90

Bạn không cần phải có nhiều đăng ký và hủy đăng ký theo cách thủ công. Sử dụng kết hợp Chủ đềTakeUntil để xử lý các đăng ký như một ông chủ:

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()
  }
}

Cách tiếp cận khác , được đề xuất bởi @acumartini trong các bình luận , sử dụng TakeWhile thay vì TakeUntil . Bạn có thể thích nó, nhưng lưu ý rằng theo cách này, việc thực thi có thể quan sát của bạn sẽ không bị hủy trên ngDestroy của thành phần của bạn (ví dụ: khi bạn thực hiện tính toán tốn thời gian hoặc chờ dữ liệu từ máy chủ). Phương thức, dựa trên TakeUntil , không có nhược điểm này và dẫn đến hủy yêu cầu ngay lập tức. Cảm ơn @AlexChe đã giải thích chi tiết trong các bình luận .

Vì vậy, đây là mã:

@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
  }
}

1
Nếu anh ta chỉ sử dụng một bool để giữ trạng thái, làm thế nào để "TakeUntil" hoạt động như mong đợi?
Val

5
Tôi nghĩ rằng có một sự khác biệt đáng kể giữa việc sử dụng takeUntiltakeWhile. Những người hủy đăng ký trước từ nguồn có thể quan sát được ngay lập tức khi bị sa thải, trong khi những người hủy đăng ký sau chỉ đăng ký ngay khi giá trị tiếp theo được tạo ra bởi nguồn có thể quan sát được. Nếu việc tạo ra một giá trị theo nguồn có thể quan sát được là một hoạt động tiêu tốn tài nguyên, việc chọn giữa hai giá trị có thể vượt quá sở thích kiểu. Xem plunk
Alex Che

1
@AlexChe cảm ơn vì đã cung cấp plunk thú vị! Đây là điểm rất hợp lệ cho việc sử dụng chung của takeUntilvs takeWhile, tuy nhiên, không phải cho trường hợp cụ thể của chúng tôi. Khi chúng ta cần hủy đăng ký người nghe về phá hủy thành phần , chúng ta chỉ kiểm tra giá trị boolean như () => alivetrong takeWhile, vì vậy mọi thao tác tiêu tốn thời gian / bộ nhớ không được sử dụng và sự khác biệt khá nhiều về kiểu dáng (đối với trường hợp cụ thể này).
siêu nhân

1
@metamaker Giả sử, trong thành phần của chúng tôi, chúng tôi đăng ký một Observable, trong đó khai thác một số loại tiền điện tử và thực hiện một nextsự kiện cho mỗi đồng tiền được khai thác và việc khai thác một đồng tiền như vậy mất một ngày. Với takeUntilchúng tôi sẽ hủy đăng ký khai thác nguồn Observablengay lập tức một lần ngOnDestroyđược gọi trong quá trình phá hủy thành phần của chúng tôi. Do đó, Observablechức năng khai thác có thể hủy bỏ hoạt động ngay lập tức trong quá trình này.
Alex Che

1
OTOH, nếu chúng ta sử dụng takeWhile, trong phần ngOnDestorychúng ta chỉ cần đặt biến boolean. Nhưng Observablechức năng khai thác có thể vẫn hoạt động đến một ngày và chỉ sau đó trong nextcuộc gọi, nó mới nhận ra rằng không có đăng ký nào hoạt động và nó cần phải hủy.
Alex Che

76

Lớp Đăng ký có một tính năng thú vị:

Đại diện cho một tài nguyên dùng một lần, chẳng hạn như việc thực thi một Đài quan sát. Đăng ký có một phương thức quan trọng, hủy đăng ký, không cần đối số và chỉ xử lý tài nguyên do đăng ký nắm giữ.
Ngoài ra, các đăng ký có thể được nhóm lại với nhau thông qua phương thức add (), sẽ đăng ký một Đăng ký con với Đăng ký hiện tại. Khi Đăng ký không được đăng ký, tất cả con cái của nó (và cháu của nó) cũng sẽ bị hủy đăng ký.

Bạn có thể tạo một đối tượng Đăng ký tổng hợp để nhóm tất cả các đăng ký của bạn. Bạn làm điều này bằng cách tạo Đăng ký trống và thêm đăng ký vào đó bằng add()phương thức của nó . Khi thành phần của bạn bị hủy, bạn chỉ cần hủy đăng ký tổng hợp.

@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();
  }
}

Tôi đang sử dụng phương pháp này. Tự hỏi liệu điều này có tốt hơn việc sử dụng cách tiếp cận với TakeUntil (), như trong câu trả lời được chấp nhận .. nhược điểm không?
Manuel Di Iorio

Không có nhược điểm mà tôi biết. Tôi không nghĩ rằng điều này là tốt hơn, chỉ khác nhau.
Steven Liekens

2
Xem Medium.com/@ben Meat/rxjs-dont-unsubscribe-6753ed4fda87 để thảo luận thêm về takeUntilcách tiếp cận chính thức so với phương pháp thu thập đăng ký và gọi điện này unsubscribe. (Cách tiếp cận này có vẻ sạch sẽ hơn đối với tôi.)
Josh Kelley

3
Một lợi ích nhỏ của câu trả lời này: bạn không phải kiểm tra xem có phải this.subscriptionskhông
user2023861

1
Chỉ cần tránh xâu chuỗi các phương thức thêm như sub = subsciption.add(..).add(..)trong nhiều trường hợp, nó tạo ra kết quả không mong muốn github.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov

32

Một số thực tiễn tốt nhất liên quan đến việc hủy đăng ký quan sát bên trong các thành phần Angular:

Một trích dẫn từ Routing & Navigation

Khi đăng ký một quan sát trong một thành phần, bạn hầu như luôn luôn sắp xếp để hủy đăng ký khi thành phần bị phá hủy.

Có một vài quan sát đặc biệt mà điều này là không cần thiết. Các đài quan sát ActivatedRoute nằm trong số các trường hợp ngoại lệ.

ActivatedRoute và các thiết bị quan sát của nó được cách ly từ chính Bộ định tuyến. Bộ định tuyến sẽ phá hủy một thành phần được định tuyến khi không còn cần thiết và ActivatedRoute được tiêm sẽ chết cùng với nó.

Hãy bỏ đăng ký nào. Nó vô hại và không bao giờ là một thực hành xấu.

Và trong việc trả lời các liên kết sau:

Tôi đã thu thập một số thực tiễn tốt nhất liên quan đến việc hủy đăng ký quan sát bên trong các thành phần Angular để chia sẻ với bạn:

  • httphủy đăng ký có thể quan sát là có điều kiện và chúng ta nên xem xét các tác động của 'cuộc gọi lại đăng ký' được chạy sau khi thành phần bị phá hủy trong từng trường hợp. Chúng tôi biết rằng hủy đăng ký góc và làm sạch httpchính nó có thể quan sát được (1) , (2) . Trong khi điều này là đúng từ góc độ của tài nguyên, nó chỉ kể một nửa câu chuyện. Giả sử chúng ta đang nói về việc gọi trực tiếp httptừ bên trong một thành phần và httpphản hồi mất nhiều thời gian hơn mức cần thiết để người dùng đóng thành phần đó. Cácsubscribe()handler vẫn sẽ được gọi ngay cả khi thành phần được đóng và hủy. Điều này có thể có tác dụng phụ không mong muốn và trong các trường hợp xấu hơn làm cho trạng thái ứng dụng bị phá vỡ. Nó cũng có thể gây ra ngoại lệ nếu mã trong cuộc gọi lại cố gắng gọi một cái gì đó vừa được xử lý. Tuy nhiên đồng thời đôi khi họ mong muốn. Giống như, giả sử bạn đang tạo một ứng dụng email và bạn kích hoạt âm thanh khi gửi email xong - bạn vẫn muốn điều đó xảy ra ngay cả khi thành phần bị đóng ( 8 ).
  • Không cần hủy đăng ký quan sát mà hoàn thành hoặc lỗi. Tuy nhiên, không có hại khi làm như vậy (7) .
  • Sử dụng AsyncPipecàng nhiều càng tốt bởi vì nó tự động hủy đăng ký khỏi sự phá hủy thành phần có thể quan sát được.
  • Hủy đăng ký khỏi các đài ActivatedRoutequan sát như route.paramsnếu chúng được đăng ký bên trong một lồng (Được thêm vào bên trong tpl với bộ chọn thành phần) hoặc thành phần động vì chúng có thể được đăng ký nhiều lần miễn là thành phần cha / máy chủ tồn tại. Không cần hủy đăng ký chúng trong các tình huống khác như được đề cập trong trích dẫn ở trên từ các Routing & Navigationtài liệu.
  • Hủy đăng ký khỏi các đài quan sát toàn cầu được chia sẻ giữa các thành phần được hiển thị thông qua dịch vụ Angular chẳng hạn vì chúng có thể được đăng ký nhiều lần miễn là thành phần được khởi tạo.
  • Không cần hủy đăng ký khỏi các thiết bị quan sát nội bộ của dịch vụ trong phạm vi ứng dụng vì dịch vụ này không bao giờ bị hủy, trừ khi toàn bộ ứng dụng của bạn bị hủy, không có lý do thực sự nào để hủy đăng ký và không có khả năng rò rỉ bộ nhớ. (6) .

    Lưu ý: Về các dịch vụ phạm vi, tức là nhà cung cấp thành phần, chúng bị hủy khi thành phần bị hủy. Trong trường hợp này, nếu chúng tôi đăng ký bất kỳ thứ gì có thể quan sát được bên trong nhà cung cấp này, chúng tôi nên xem xét hủy đăng ký sử dụng OnDestroymóc vòng đời sẽ được gọi khi dịch vụ bị hủy, theo tài liệu.
  • Sử dụng một kỹ thuật trừu tượng để tránh bất kỳ sự lộn xộn mã nào có thể xảy ra do việc hủy đăng ký. Bạn có thể quản lý đăng ký của mình với takeUntil (3) hoặc bạn có thể sử dụng npm gói này được đề cập tại (4) Cách dễ nhất để hủy đăng ký khỏi Đài quan sát trong Angular .
  • Luôn hủy đăng ký từ FormGroupcác quan sát như form.valueChangesform.statusChanges
  • Luôn hủy đăng ký khỏi các Renderer2dịch vụ quan sát nhưrenderer2.listen
  • Hủy đăng ký khỏi mọi thứ khác có thể quan sát được như một bước bảo vệ rò rỉ bộ nhớ cho đến khi Angular Docs cho chúng ta biết rõ ràng những vật quan sát nào không cần thiết phải hủy đăng ký (Kiểm tra vấn đề: (5) Tài liệu cho RxJS Hủy đăng ký (Mở) ).
  • Phần thưởng: Luôn sử dụng các cách Angular để liên kết các sự kiện như HostListenerangular quan tâm tốt đến việc loại bỏ các trình lắng nghe sự kiện nếu cần và ngăn chặn bất kỳ rò rỉ bộ nhớ tiềm năng nào do các ràng buộc sự kiện.

Một mẹo hay cuối cùng : Nếu bạn không biết liệu có thể quan sát được tự động hủy đăng ký / hoàn thành hay không, hãy thêm một completecuộc gọi lại subscribe(...)và kiểm tra xem nó có được gọi khi thành phần bị phá hủy không.


Trả lời cho số 6 không hoàn toàn đúng. Các dịch vụ bị hủy và chúng ngOnDestroyđược gọi khi dịch vụ được cung cấp ở cấp độ khác với cấp gốc, ví dụ được cung cấp rõ ràng trong một thành phần mà sau đó bị xóa. Trong những trường hợp này, bạn nên hủy đăng ký khỏi các dịch vụ quan sát bên trong
Drenai

@Drenai, cảm ơn vì nhận xét của bạn và lịch sự tôi không đồng ý. Nếu một thành phần bị phá hủy, thành phần, dịch vụ và có thể quan sát được sẽ bị hủy toàn bộ và việc hủy đăng ký sẽ vô dụng trong trường hợp này trừ khi bạn giữ một tham chiếu cho bất kỳ nơi nào có thể quan sát được ở bất kỳ nơi nào (Không hợp lý để rò rỉ các trạng thái thành phần trên toàn cầu mặc dù đã phân chia dịch vụ cho thành phần)
Mouneer

Nếu dịch vụ bị hủy có đăng ký vào một dịch vụ có thể quan sát thuộc về dịch vụ khác cao hơn trong hệ thống phân cấp DI, thì GC sẽ không xảy ra. Tránh trường hợp này bằng cách hủy đăng ký ngOnDestroy, luôn được gọi khi dịch vụ bị hủy github.com/angular/angular/commit/
ám

1
@Drenai, Kiểm tra câu trả lời cập nhật.
Mouneer

2
@Tim Trước hết, Feel free to unsubscribe anyway. It is harmless and never a bad practice.và liên quan đến câu hỏi của bạn, nó phụ thuộc. Nếu thành phần con được khởi tạo nhiều lần (Ví dụ: được thêm vào bên trong ngIfhoặc được tải động), bạn phải hủy đăng ký để tránh thêm nhiều đăng ký vào cùng một người quan sát. Nếu không thì không cần. Nhưng tôi thích hủy đăng ký bên trong thành phần con vì điều này làm cho nó có thể tái sử dụng nhiều hơn và tách biệt với cách sử dụng nó.
Mouneer

18

Nó phụ thuộc. Nếu bằng cách gọi someObservable.subscribe(), bạn bắt đầu giữ một số tài nguyên phải được giải phóng thủ công khi vòng đời của thành phần của bạn kết thúc, thì bạn nên gọi theSubscription.unsubscribe()để tránh rò rỉ bộ nhớ.

Hãy xem xét kỹ hơn các ví dụ của bạn:

getHero()trả về kết quả của http.get(). Nếu bạn nhìn vào mã nguồn 2 góc , hãy http.get()tạo hai trình lắng nghe sự kiện:

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

và bằng cách gọi unsubscribe(), bạn có thể hủy yêu cầu cũng như người nghe:

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

Lưu ý rằng đó _xhrlà nền tảng cụ thể nhưng tôi nghĩ an toàn khi cho rằng đó là một XMLHttpRequest()trường hợp của bạn.

Thông thường, đây là bằng chứng đủ để đảm bảo một unsubscribe()cuộc gọi thủ công. Nhưng theo thông số WHATWG này , XMLHttpRequest()đối tượng được thu gom rác một khi nó được "thực hiện", ngay cả khi có các trình lắng nghe sự kiện được đính kèm. Vì vậy, tôi đoán đó là lý do tại sao 2 hướng dẫn chính thức bỏ qua unsubscribe()và cho phép GC làm sạch người nghe.

Đối với ví dụ thứ hai của bạn, nó phụ thuộc vào việc thực hiện params. Cho đến hôm nay, hướng dẫn chính thức góc cạnh không còn hiển thị hủy đăng ký params. Tôi nhìn vào src một lần nữa và thấy rằng đó paramschỉ là một BehaviorSubject . Vì không có trình lắng nghe hoặc bộ tính giờ nào được sử dụng và không có biến toàn cục nào được tạo, nên sẽ an toàn khi bỏ qua unsubscribe().

Điểm mấu chốt cho câu hỏi của bạn là luôn gọi unsubscribe()là người bảo vệ chống rò rỉ bộ nhớ, trừ khi bạn chắc chắn rằng việc thực thi có thể quan sát được không tạo ra các biến toàn cục, thêm trình lắng nghe sự kiện, đặt bộ hẹn giờ hoặc làm bất cứ điều gì khác dẫn đến rò rỉ bộ nhớ .

Khi nghi ngờ, hãy nhìn vào việc thực hiện có thể quan sát được. Nếu quan sát được đã viết một số logic dọn dẹp vào nó unsubscribe(), thường là hàm được trả về bởi hàm tạo, thì bạn có lý do chính đáng để xem xét nghiêm túc việc gọi unsubscribe().


6

Tài liệu chính thức của Angular 2 cung cấp một lời giải thích khi nào nên hủy đăng ký và khi nào có thể bỏ qua một cách an toàn. Có một liên kết tốt:

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

Tìm đoạn có tiêu đề Phụ huynh và trẻ em giao tiếp qua một dịch vụ và sau đó là hộp màu xanh:

Lưu ý rằng chúng tôi nắm bắt được đăng ký và hủy đăng ký khi AstronautComponent bị phá hủy. Đây là một bước bảo vệ rò rỉ bộ nhớ. Không có rủi ro thực sự trong ứng dụng này vì thời gian tồn tại của một phi hành gia tương đương với thời gian tồn tại của chính ứng dụng. Điều đó không phải lúc nào cũng đúng trong một ứng dụng phức tạp hơn.

Chúng tôi không thêm bảo vệ này vào MissionControlComponent bởi vì, với tư cách là cha mẹ, nó sẽ kiểm soát vòng đời của MissionService.

Tôi hy vọng cái này sẽ giúp bạn.


3
như một thành phần bạn không bao giờ biết liệu bạn có phải là một đứa trẻ hay không. do đó bạn nên luôn luôn hủy đăng ký từ đăng ký là cách tốt nhất.
Nghiêm túc

1
Quan điểm về MissionControlComponent không thực sự là về việc nó có phải là cha mẹ hay không, mà chính thành phần đó cung cấp dịch vụ. Khi MissionControl bị hủy, dịch vụ và mọi tham chiếu đến thể hiện của dịch vụ cũng vậy, do đó không có khả năng bị rò rỉ.
Ender

6

Dựa trên: Sử dụng kế thừa Class để móc vào vòng đời thành phần Angular 2

Một cách tiếp cận chung khác:

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
  }

}

Và sử dụng :

@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));
    }
}


1
Điều này KHÔNG hoạt động chính xác. Hãy cẩn thận khi sử dụng giải pháp này. Bạn đang thiếu một this.componentDestroyed$.next()cuộc gọi như giải pháp được chấp nhận bởi sean ở trên ...
philn

4

Câu trả lời chính thức số 3 (và các biến thể) hoạt động tốt, nhưng điều khiến tôi hiểu là 'sự nhầm lẫn' của logic kinh doanh xung quanh thuê bao quan sát được.

Đây là một cách tiếp cận khác bằng cách sử dụng trình bao bọc.

Warining: mã thử nghiệm

Tệp subscribeAndGuard.ts được sử dụng để tạo một tiện ích mở rộng quan sát mới để bọc .subscribe()và bên trong nó để bọc ngOnDestroy().
Cách sử dụng cũng giống như .subscribe(), ngoại trừ một tham số đầu tiên bổ sung tham chiếu thành phần.

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;
  }
}

Đây là một thành phần có hai mục đăng ký, một với trình bao bọc và một không có. Nhắc nhở duy nhất là nó phải thực hiện OnDestroy (với phần thân trống nếu muốn), nếu không thì Angular không biết gọi phiên bản được bao bọc.

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');
  }
}

Một plunker demo là ở đây

Một lưu ý bổ sung: Chỉnh sửa lại 3 - Giải pháp 'Chính thức', điều này có thể được đơn giản hóa bằng cách sử dụng TakeWhile () thay vì TakeUntil () trước khi đăng ký và một boolean đơn giản thay vì có thể quan sát được khác trong 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;
  }
}

3

Vì giải pháp của seangwright (Chỉnh sửa 3) có vẻ rất hữu ích, tôi cũng cảm thấy khó khăn khi đóng gói tính năng này vào thành phần cơ bản và gợi ý các đồng đội dự án khác nhớ gọi super () trên ngOnDestroy để kích hoạt tính năng này.

Câu trả lời này cung cấp một cách để giải phóng khỏi siêu cuộc gọi và biến "thành phầnDestroyed $" thành cốt lõi của thành phần cơ sở.

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() {}
}

Và sau đó bạn có thể sử dụng tính năng này một cách tự do:

@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');
    }

}

3

Theo câu trả lời của @seangwright , tôi đã viết một lớp trừu tượng xử lý các đăng ký "vô hạn" trong các thành phần:

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();
  }
}

Để sử dụng nó, chỉ cần mở rộng nó trong thành phần góc của bạn và gọi subscribe()phương thức như sau:

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

Nó cũng chấp nhận lỗi và hoàn thành các cuộc gọi lại như bình thường, một đối tượng quan sát hoặc hoàn toàn không gọi lại. Hãy nhớ gọi super.ngOnDestroy()nếu bạn cũng đang thực hiện phương thức đó trong thành phần con.

Tìm ở đây một tài liệu tham khảo bổ sung bởi Ben Lesh: RxJS: Đừng hủy đăng ký .


2

Tôi đã thử giải pháp của seangwright (Chỉnh sửa 3)

Điều đó không làm việc cho Observable được tạo bởi bộ đếm thời gian hoặc khoảng thời gian.

Tuy nhiên, tôi đã làm cho nó hoạt động bằng cách sử dụng một phương pháp khác:

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();
   }
 }
}

2

Tôi giống như hai câu trả lời cuối cùng, nhưng tôi gặp phải sự cố nếu phân lớp tham chiếu "this"trong ngOnDestroy.

Tôi đã sửa đổi nó thành cái này, và có vẻ như nó đã giải quyết vấn đề đó.

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() {}
}

bạn cần sử dụng chức năng mũi tên để liên kết 'cái này':this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damuptian

2

Trong trường hợp hủy đăng ký là cần thiết, có thể sử dụng toán tử sau cho phương pháp đường ống quan sát được

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));
};

nó có thể được sử dụng như thế này:

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 {
  }
}

Toán tử kết thúc phương thức ngOnDestroy của thành phần.

Quan trọng: người vận hành phải là người cuối cùng trong đường ống quan sát được.


Điều này làm việc rất tốt, tuy nhiên việc nâng cấp lên 9 góc dường như giết chết nó. Có ai biết tại sao không?
y 4.0.3j

1

Bạn thường cần hủy đăng ký khi các thành phần bị phá hủy, nhưng Angular sẽ xử lý nó ngày càng nhiều hơn, ví dụ như trong phiên bản nhỏ mới của Angular4, chúng có phần này để định tuyến hủy đăng ký:

Bạn có cần hủy đăng ký?

Như được mô tả trong ActivatedRoute: phần một cửa cho thông tin tuyến đường của trang Định tuyến & Điều hướng, Bộ định tuyến quản lý các quan sát mà nó cung cấp và bản địa hóa các đăng ký. Các đăng ký được dọn sạch khi thành phần bị phá hủy, bảo vệ chống rò rỉ bộ nhớ, do đó bạn không cần hủy đăng ký khỏi tuyến đường paramMap có thể quan sát được.

Ngoài ra ví dụ dưới đây là một ví dụ hay từ Angular để tạo ra một thành phần và phá hủy nó sau đó, hãy xem cách thành phần thực hiện OnDestroy, nếu bạn cần onInit, bạn cũng có thể thực hiện nó trong thành phần của mình, như thực hiện 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();
  }
}

3
Bối rối. Bạn đang nói gì ở đây? Bạn (tài liệu / ghi chú gần đây của Angular) dường như nói rằng Angular chăm sóc nó và sau đó để xác nhận rằng hủy đăng ký là một mô hình tốt. Cảm ơn.
jamie

1

Một bổ sung ngắn khác cho các tình huống nêu trên là:

  • Luôn hủy đăng ký, khi các giá trị mới trong luồng được đăng ký không còn cần thiết hoặc không quan trọng, điều đó sẽ dẫn đến số lượng kích hoạt ít hơn và tăng hiệu suất trong một vài trường hợp. Các trường hợp như các thành phần trong đó dữ liệu / sự kiện đã đăng ký không còn tồn tại hoặc đăng ký mới cho một luồng hoàn toàn mới là bắt buộc (làm mới, v.v.) là một ví dụ tốt cho việc hủy đăng ký.

0

trong ứng dụng SPA tại chức năng ngOnDestroy (vòng đời góc cạnh) Đối với mỗi lần đăng ký, bạn cần hủy đăng ký nó. lợi thế => để ngăn chặn nhà nước trở nên quá nặng.

ví dụ: trong thành phần1:

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

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

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

phục vụ:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

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

trong thành phần 2:

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();
}

0

Để xử lý đăng ký, tôi sử dụng lớp "Unsubscacker".

Đây là lớp Unsubscacker.

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();
  }
}

Và bạn có thể sử dụng lớp này trong bất kỳ thành phần / Dịch vụ / Hiệu ứng, v.v.

Thí dụ:

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

    this.addSubscription(subscription);
}

0

Bạn có thể sử dụng Subscriptionlớp mới nhất để hủy đăng ký cho Đài quan sát với mã không quá lộn xộn.

Chúng tôi có thể làm điều này với normal variablenhưng nó sẽ có override the last subscriptiontrong mỗi lần đăng ký mới để tránh điều đó, và cách tiếp cận này rất hữu ích khi bạn đang xử lý số lượng Đài quan sát nhiều hơn và loại Béo phì như BehavoiurSubjectSubject

Đăng ký

Đại diện cho một tài nguyên dùng một lần, chẳng hạn như việc thực thi một Đài quan sát. Đăng ký có một phương thức quan trọng, hủy đăng ký, không cần đối số và chỉ xử lý tài nguyên do đăng ký nắm giữ.

bạn có thể sử dụng điều này theo hai cách,

  • bạn có thể trực tiếp đẩy đăng ký vào mảng đăng ký

     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());
      }
    
  • sử dụng add()củaSubscription

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

A Subscriptioncó thể giữ đăng ký con và hủy đăng ký tất cả chúng một cách an toàn. Phương pháp này xử lý các lỗi có thể xảy ra (ví dụ: nếu bất kỳ đăng ký con nào là null).

Hi vọng điêu nay co ich.. :)


0

Gói SubSink, một giải pháp dễ dàng và nhất quán để hủy đăng ký

Như chưa có ai khác đề cập đến nó, tôi muốn giới thiệu gói subsink được tạo bởi Ward Bell: https://github.com/wardbell/subink#readme .

Tôi đã sử dụng nó trong một dự án vì chúng tôi là một số nhà phát triển sử dụng nó. Nó giúp rất nhiều để có một cách nhất quán hoạt động trong mọi tình huống.


0

Đối với các thiết bị quan sát hoàn thành trực tiếp sau khi phát ra kết quả như AsyncSubjecthoặc ví dụ có thể quan sát được từ các yêu cầu http và như vậy bạn không cần hủy đăng ký. Sẽ không đau khi gọi unsubscribe()cho những người đó, nhưng nếu có thể quan sát được closedthì phương thức hủy đăng ký sẽ đơn giản là không làm gì cả :

if (this.closed) {
  return;
}

Khi bạn có các vật quan sát tồn tại lâu phát ra một số giá trị theo thời gian (ví dụ như a BehaviorSubjecthoặc a ReplaySubject), bạn cần hủy đăng ký để tránh rò rỉ bộ nhớ.

Bạn có thể dễ dàng tạo một vật quan sát hoàn thành trực tiếp sau khi phát ra kết quả từ các vật quan sát tồn tại lâu như vậy bằng cách sử dụng toán tử đường ống. Trong một số câu trả lời ở đây, take(1)đường ống được đề cập. Nhưng tôi thích các first()ống . Sự khác biệt take(1)là nó sẽ:

gửi EmptyErrorlại cho cuộc gọi lại lỗi của Người quan sát nếu Quan sát hoàn thành trước khi bất kỳ thông báo tiếp theo nào được gửi.

Một ưu điểm khác của đường ống đầu tiên là bạn có thể vượt qua một vị từ sẽ giúp bạn trả về giá trị đầu tiên thỏa mãn các tiêu chí nhất định:

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);

Đầu tiên sẽ hoàn thành trực tiếp sau khi phát ra giá trị đầu tiên (hoặc khi truyền đối số hàm, giá trị đầu tiên thỏa mãn vị từ của bạn), do đó không cần hủy đăng ký.

Đôi khi bạn không chắc chắn về việc bạn có sống lâu hay không. Tôi không nói rằng đó là một thực tiễn tốt nhưng sau đó bạn luôn có thể thêm firstđường ống chỉ để đảm bảo rằng bạn sẽ không cần phải hủy đăng ký theo cách thủ công. Thêm một firstđường ống bổ sung vào một vật quan sát sẽ chỉ phát ra một giá trị không gây hại.

Trong phát triển, bạn có thể sử dụng các singleống đó sẽ thất bại nếu nguồn phát ra quan sát được một số sự kiện. Điều này có thể giúp bạn khám phá loại có thể quan sát được và liệu có cần thiết phải hủy đăng ký từ nó hay không.

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

Các firstsingledường như rất giống nhau, cả hai đường ống có thể mất một vị ngữ bắt buộc nhưng sự khác biệt rất quan trọng và độc đáo tóm tắt trong stackoverflow câu trả lời này đây :

Đầu tiên

Sẽ phát ra ngay khi mục đầu tiên xuất hiện. Sẽ hoàn thành ngay sau đó.

Độc thân

Sẽ thất bại nếu nguồn quan sát phát ra một số sự kiện.


Lưu ý Tôi đã cố gắng chính xác và đầy đủ nhất có thể trong câu trả lời của mình với các tham chiếu đến tài liệu chính thức, nhưng vui lòng nhận xét nếu thiếu nội dung quan trọng ...


-1

--- Cập nhật Giải pháp Angular 9 và Rxjs 6

  1. Sử dụng unsubscribetại ngDestroyvòng đời của Thành phần góc
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

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

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. Sử dụng takeUntiltrong 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. đối với một số hành động mà bạn gọi lúc ngOnInitđó chỉ xảy ra một lần khi thành phần init.
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

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

Chúng tôi cũng có asyncđường ống. Nhưng, cái này sử dụng trên mẫu (không phải trong thành phần Angular).


Ví dụ đầu tiên của bạn là không đầy đủ.
Paul-Sebastian Manole
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.