Angular / RxJs ฉันควรยกเลิกการสมัครเมื่อ


720

ฉันควรจัดเก็บSubscriptionอินสแตนซ์และเรียกใช้unsubscribe()ในช่วงชีวิตของ NgOnDestroy เมื่อใดและเมื่อใดที่ฉันจะเพิกเฉยได้

การบันทึกการสมัครสมาชิกทั้งหมดทำให้เกิดความยุ่งเหยิงในรหัสส่วนประกอบ

คู่มือไคลเอนต์ HTTPละเว้นการสมัครสมาชิกดังนี้:

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

ในขณะเดียวกันคำแนะนำเส้นทางและการนำทางบอกว่า:

ในที่สุดเราจะนำทางไปที่อื่น เราเตอร์จะลบส่วนประกอบนี้ออกจาก DOM และทำลายมัน เราต้องทำความสะอาดตัวเองก่อนที่จะเกิดขึ้น โดยเฉพาะเราต้องยกเลิกการสมัครก่อนที่ Angular จะทำลายส่วนประกอบ การไม่ทำเช่นนั้นอาจทำให้หน่วยความจำรั่ว

เรายกเลิกการเป็นสมาชิกของเราObservableในngOnDestroyวิธีการ

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
ผมคิดว่าSubscriptionที่จะhttp-requestsสามารถปฏิเสธเช่นที่พวกเขาเพียงโทรครั้งเดียวแล้วที่พวกเขาเรียกonNext แทนที่จะเรียกร้องซ้ำแล้วซ้ำอีกและไม่อาจเรียก(ไม่แน่ใจเกี่ยวกับที่ ... ) กันไปจากs ดังนั้นผมคิดว่าผู้ที่ควรจะเป็น onCompleteRouteronNextonCompleteObservableEventunsubscribed
Springrbua

1
@ gt6707a สตรีมเสร็จสมบูรณ์ (หรือไม่สมบูรณ์) โดยไม่ขึ้นกับการสังเกตใด ๆ ของความสมบูรณ์นั้น การเรียกกลับ (ผู้สังเกตการณ์) ที่จัดให้กับฟังก์ชันสมัครสมาชิกไม่ได้กำหนดว่าจะจัดสรรทรัพยากรหรือไม่ เป็นการเรียกsubscribeตัวเองว่าอาจจัดสรรทรัพยากรต้นน้ำ
seangwright

คำตอบ:


981

--- แก้ไข 4 - แหล่งข้อมูลเพิ่มเติม (2018/09/01)

ในตอนล่าสุดของการผจญภัยในเชิงมุมเบนเลชและวอร์ดเบลล์หารือเกี่ยวกับประเด็นต่างๆว่าจะยกเลิกการสมัครสมาชิกในองค์ประกอบอย่างไร การสนทนาเริ่มต้นที่ประมาณ 1:05:30 น.

วอร์ดกล่าวright now there's an awful takeUntil dance that takes a lot of machineryและ Shai Reznik Angular handles some of the subscriptions like http and routingกล่าว

ในการตอบกลับเบ็นกล่าวว่าขณะนี้มีการหารือกันเพื่อให้ Observables สามารถเชื่อมต่อกับวงจรชีวิตขององค์ประกอบเชิงมุมและ Ward แนะนำให้สังเกตเหตุการณ์รอบการทำงานที่องค์ประกอบสามารถสมัครสมาชิกเพื่อให้ทราบว่าเมื่อใดที่ Observables ได้รับการดูแลในฐานะองค์ประกอบภายใน

ที่กล่าวว่าเราต้องการโซลูชันเป็นส่วนใหญ่ดังนั้นนี่คือแหล่งข้อมูลอื่น

  1. คำแนะนำสำหรับtakeUntil()รูปแบบจากสมาชิกในทีมหลักของ RxJs Nicholas Jamieson และกฎ tslint เพื่อช่วยบังคับใช้ https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. แพคเกจ NPM น้ำหนักเบาที่ exposes ผู้ประกอบการสังเกตที่ใช้อินสแตนซ์ส่วนประกอบ ( this) ngOnDestroyเป็นพารามิเตอร์โดยอัตโนมัติและการยกเลิกการสมัครในช่วง https://github.com/NetanelBasal/ngx-take-until-destroy

  3. อีกรูปแบบหนึ่งของด้านบนที่มีการยศาสตร์ที่ดีขึ้นเล็กน้อยถ้าคุณไม่ได้สร้าง AOT (แต่เราควรจะทำ AOT ตอนนี้) https://github.com/smnbbrv/ngx-rx-collector

  4. คำสั่งที่กำหนดเอง*ngSubscribeที่ทำงานเหมือนไปป์ async แต่สร้างมุมมองแบบฝังในแม่แบบของคุณเพื่อให้คุณสามารถอ้างถึงค่า 'ไม่ได้ห่อ' ตลอดทั้งแม่แบบของคุณ https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

ฉันพูดถึงในความคิดเห็นในบล็อกของนิโคลัสที่มากกว่าการใช้takeUntil()อาจเป็นสัญญาณว่าองค์ประกอบของคุณพยายามที่จะทำมากเกินไปและแยกชิ้นส่วนที่มีอยู่ของคุณลงในคุณสมบัติและPresentationalส่วนประกอบควรได้รับการพิจารณา จากนั้นคุณสามารถ| asyncสังเกตเห็นได้จากองค์ประกอบคุณสมบัติในInputองค์ประกอบปัจจุบันซึ่งหมายความว่าไม่จำเป็นต้องสมัครสมาชิกใด ๆ อ่านเพิ่มเติมเกี่ยวกับวิธีการนี้ได้ที่นี่

--- แก้ไข 3 - ทางออก 'เป็นทางการ' (2017/04/52)

ฉันได้พูดคุยกับ Ward Bell เกี่ยวกับคำถามนี้ที่ NGConf (ฉันยังแสดงให้เขาเห็นคำตอบนี้ซึ่งเขาพูดถูกต้อง) แต่เขาบอกกับฉันว่าทีมเอกสารสำหรับ Angular มีวิธีแก้ปัญหาสำหรับคำถามนี้ที่ไม่ได้เผยแพร่ (แม้ว่าพวกเขากำลังทำงาน ) เขายังบอกฉันว่าฉันสามารถอัพเดตคำตอบ SO ของฉันด้วยคำแนะนำอย่างเป็นทางการที่กำลังจะมาถึง

ทางออกที่เราทุกคนควรใช้ในอนาคตคือการเพิ่มprivate ngUnsubscribe = new Subject();เขตข้อมูลให้กับส่วนประกอบทั้งหมดที่มีการ.subscribe()เรียกไปยังObservables ภายในรหัสชั้นเรียนของพวกเขา

จากนั้นเราโทรthis.ngUnsubscribe.next(); this.ngUnsubscribe.complete();ในngOnDestroy()วิธีการของเรา

ซอสลับ (ตามที่ระบุไว้แล้วโดย@metamaker ) คือการโทรtakeUntil(this.ngUnsubscribe)ก่อนการโทรแต่ละครั้งของเรา.subscribe()ซึ่งจะรับประกันการสมัครสมาชิกทั้งหมดจะถูกล้างออกเมื่อส่วนประกอบถูกทำลาย

ตัวอย่าง:

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

หมายเหตุ:มันเป็นสิ่งสำคัญที่จะเพิ่มtakeUntilผู้ประกอบการเป็นคนสุดท้ายเพื่อป้องกันการรั่วไหลด้วยสังเกตกลางในห่วงโซ่ผู้ประกอบการ

--- แก้ไข 2 (2016/12/28)

ที่มา 5

บทช่วยสอนเชิง Angular ตอนนี้ระบุถึงสิ่งต่อไปนี้: "เราเตอร์จัดการสิ่งที่สังเกตได้และให้การสมัครสมาชิกการแปลจะถูกล้างเมื่อส่วนประกอบถูกทำลายเพื่อป้องกันการรั่วไหลของหน่วยความจำดังนั้นเราจึงไม่จำเป็นต้องยกเลิกการสมัคร params เส้นทางสังเกตได้ " - Mark Rajcok

นี่คือการอภิปรายเกี่ยวกับปัญหา Github สำหรับเอกสารเชิงมุมเกี่ยวกับการสังเกตเราเตอร์ที่ Ward Bell ระบุว่าการชี้แจงทั้งหมดนี้อยู่ในผลงาน

--- แก้ไข 1

ที่มา 4

ในวิดีโอนี้จาก NgEurope Rob Wormald ยังบอกด้วยว่าคุณไม่จำเป็นต้องยกเลิกการสมัครรับข้อมูลจาก Router Observables นอกจากนี้เขายังกล่าวถึงhttpการให้บริการและActivatedRoute.paramsในครั้งนี้วิดีโอจากพฤศจิกายน 2016

--- คำตอบเดิม

TLDR:

สำหรับคำถามนี้มี (2) ชนิดObservables- ค่าจำกัดและค่าไม่ จำกัด

http Observablesสร้างค่า จำกัด (1) และบางอย่างเช่น DOM event listener Observablesสร้างค่าไม่สิ้นสุด

ถ้าคุณโทรsubscribe(ไม่ได้ใช้ท่อ async) แล้วunsubscribeจากการที่ไม่มีที่สิ้นสุด Observables

ไม่ต้องกังวลกับคนที่จำกัดRxJsจะดูแลพวกเขา

ที่มา 1

ผมติดตามลงคำตอบจากร็อบ Wormald ในเชิงมุมของ Gitter ที่นี่

เขากล่าว (ฉันจัดระเบียบใหม่เพื่อความชัดเจนและเน้นเป็นของฉัน)

หากเป็นลำดับเดียว (เช่นคำขอ http) การล้างด้วยตนเองนั้นไม่จำเป็น (สมมติว่าคุณสมัครสมาชิกในคอนโทรลเลอร์ด้วยตนเอง)

ฉันควรจะพูดว่า "ถ้ามันเป็นลำดับที่สมบูรณ์ " (ซึ่งลำดับค่าเดียวคือ la http เป็นหนึ่ง)

ถ้าลำดับอนันต์มัน , คุณควรจะยกเลิกการเป็นสมาชิกที่ท่อ async ไม่สำหรับคุณ

นอกจากนี้เขากล่าวถึงในเรื่องนี้วิดีโอ YouTubeบน observables ว่าthey clean up after themselves... ในบริบทของ observables ที่complete(เช่นสัญญาที่สมบูรณ์เสมอเพราะพวกเขามักจะผลิต 1 คุ้มค่าและสิ้นสุด - เราไม่เคยกังวลเกี่ยวกับการยกเลิกจากสัญญาที่จะทำให้แน่ใจว่าพวกเขาทำความสะอาดxhrเหตุการณ์ ผู้ฟังใช่ไหม?)

ที่มา 2

นอกจากนี้ในคู่มือ Rangle ไปที่ Angular 2 ที่อ่าน

ในกรณีส่วนใหญ่เราไม่จำเป็นต้องโทรหาวิธีการยกเลิกการสมัครอย่างชัดเจนเว้นแต่ว่าเราต้องการยกเลิกก่อนกำหนดหรือ Observable ของเรามีอายุการใช้งานนานกว่าการสมัครของเรา พฤติกรรมเริ่มต้นของโอเปอเรเตอร์ Observable คือการกำจัดการสมัครสมาชิกทันทีที่ข้อความ. comlete () หรือ .error () ถูกเผยแพร่ โปรดจำไว้ว่า RxJS ถูกออกแบบมาเพื่อใช้ใน "ไฟและลืม" แฟชั่นเกือบตลอดเวลา

วลีนี้our Observable has a longer lifespan than our subscriptionใช้เมื่อใด

มันจะใช้เมื่อการสมัครสมาชิกถูกสร้างขึ้นภายในองค์ประกอบที่ถูกทำลายก่อน (หรือไม่ 'ยาว' ก่อน) ความObservableสมบูรณ์

ฉันอ่านสิ่งนี้เป็นความหมายถ้าเราสมัครรับhttpคำขอหรือสิ่งที่สังเกตได้ที่ปล่อยออกมา 10 ค่าและองค์ประกอบของเราถูกทำลายก่อนที่httpคำขอนั้นจะส่งคืนหรือ 10 ค่านั้นถูกปล่อยออกมาเราก็ยังโอเค!

เมื่อคำขอกลับมาหรือค่าที่ 10 ถูกปล่อยออกมาในที่สุดก็Observableจะเสร็จสมบูรณ์และทรัพยากรทั้งหมดจะถูกล้างออก

ที่มา 3

หากเราดูตัวอย่างจากคู่มือ Rangle เดียวกันเราจะเห็นว่าสิ่งSubscriptionที่route.paramsต้องทำคือไม่ต้องทำunsubscribe()เพราะเราไม่รู้ว่าเมื่อใดparamsจะหยุดการเปลี่ยนแปลง (ปล่อยค่าใหม่)

องค์ประกอบที่จะถูกทำลายโดยการนำออกไปซึ่งในกรณี params เส้นทางมีแนวโน้มที่จะยังคงมีการเปลี่ยนแปลง (พวกเขาในทางเทคนิคอาจมีการเปลี่ยนแปลงจนกระทั่งปลาย app) completionและทรัพยากรที่จัดสรรในการสมัครสมาชิกจะยังคงได้รับการจัดสรรเพราะมีไม่ได้รับการ


15
การโทรcomplete()ด้วยตัวเองไม่ปรากฏขึ้นเพื่อล้างการสมัครสมาชิก อย่างไรก็ตามการโทรnext()และจากนั้นcomplete()ฉันเชื่อว่าtakeUntil()จะหยุดเมื่อมีการสร้างค่าไม่ใช่เมื่อสิ้นสุดลำดับ
Firefly

2
@ seangwright การทดสอบอย่างรวดเร็วกับสมาชิกประเภทSubjectภายในคอมโพเนนต์และสลับngIfไปมาเพื่อทริกเกอร์ngOnInitและngOnDestroyแสดงให้เห็นว่าหัวเรื่องและการสมัครรับข้อมูลจะไม่สมบูรณ์หรือได้รับการกำจัด ( finallyเชื่อมต่อกับผู้ดำเนินการสมัครสมาชิก) ฉันต้องโทรSubject.complete()เข้าngOnDestroyเพื่อให้การสมัครสมาชิกสามารถล้างข้อมูลได้ด้วยตนเอง
ลาร์ส

3
--- การแก้ไข 3ของคุณลึกซึ้งมากขอบคุณ! ฉันเพียงแค่มีคำถามติดตาม: หากใช้takeUnitlวิธีการนี้เราไม่ต้องยกเลิกการสมัครด้วยตนเองจากการสังเกตใด ๆ ? เป็นอย่างนั้นเหรอ? นอกจากนี้ทำไมเราต้องโทรหาnext()ในngOnDestroyทำไมไม่เพียงโทรcomplete()?
uglycode

6
@ seangwright มันน่าผิดหวัง สำเร็จรูปเพิ่มเติมน่ารำคาญ
spongessuck

3
แก้ไข 3 ที่กล่าวถึงในบริบทของกิจกรรมที่medium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87
HankCa

90

คุณไม่จำเป็นต้องมีการสมัครสมาชิกมากมายและยกเลิกการสมัครด้วยตนเอง ใช้หัวเรื่องและtakeUntilคอมโบเพื่อจัดการการสมัครสมาชิกเช่นเจ้านาย:

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

วิธีทางเลือกซึ่งได้รับการเสนอโดย @acumartini ในความคิดเห็นใช้takeWhileแทนtakeUntil คุณอาจชอบ แต่โปรดทราบว่าวิธีนี้การดำเนินการสังเกตของคุณจะไม่ถูกยกเลิกใน ngDestroy ของส่วนประกอบของคุณ (เช่นเมื่อคุณใช้เวลาในการคำนวณนานหรือรอข้อมูลจากเซิร์ฟเวอร์) วิธีการซึ่งยึดตามtakeUntilไม่มีข้อเสียนี้และนำไปสู่การยกเลิกคำขอทันที ขอขอบคุณที่ @AlexChe สำหรับคำอธิบายรายละเอียดในการแสดงความคิดเห็น

ดังนั้นนี่คือรหัส:

@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
หากเขาเพียงแค่ใช้บูลเพื่อรักษาสถานะวิธีการทำ "takeUntil" ทำงานตามที่คาดไว้?
Val

5
ผมคิดว่ามีความแตกต่างอย่างมีนัยสำคัญระหว่างการใช้และtakeUntil takeWhileอดีตการยกเลิกการสมัครสมาชิกจากแหล่งที่สังเกตได้ทันทีเมื่อถูกไล่ออกในขณะที่การยกเลิกการสมัครสมาชิกครั้งหลังจะเกิดขึ้นทันทีที่มีการสร้างมูลค่าถัดไปโดยแหล่งข้อมูล หากการผลิตคุณค่าโดยแหล่งที่สังเกตได้คือการดำเนินการใช้ทรัพยากรการเลือกระหว่างสองอาจเกินกว่าการกำหนดลักษณะ ดูเสียงพึมพำ
Alex Che

1
@AlexChe ขอบคุณสำหรับการให้เสียงดังปังที่น่าสนใจ! นี่เป็นจุดที่ถูกต้องมากสำหรับการใช้งานทั่วไปของtakeUntilvs takeWhileแต่ไม่ใช่สำหรับกรณีเฉพาะของเรา เมื่อเราต้องการยกเลิกการฟังผู้ฟังเกี่ยวกับการทำลายส่วนประกอบเราเพียงตรวจสอบค่าบูลีนเช่นเดียวกับ() => aliveในtakeWhileดังนั้นเวลาใด ๆ / การดำเนินการที่ใช้หน่วยความจำจะไม่ถูกนำมาใช้
เครื่องมือวัด

1
@metamaker บอกว่าในส่วนของเราเราสมัครสมาชิกObservableซึ่งภายในเหมืองบางสกุลเงินเข้ารหัสลับและยิงnextเหตุการณ์สำหรับทุกเหรียญขุดและการขุดหนึ่งเหรียญดังกล่าวใช้เวลาหนึ่งวัน ด้วยtakeUntilเราจะยกเลิกการสมัครจากแหล่งขุดObservableทันทีที่ngOnDestroyถูกเรียกในระหว่างการทำลายส่วนประกอบของเรา ดังนั้นObservableฟังก์ชั่นการขุดสามารถยกเลิกการทำงานได้ทันทีในระหว่างกระบวนการนี้
Alex Che

1
OTOH ถ้าเราใช้takeWhileในngOnDestoryเราเพียงแค่ตั้งค่าตัวแปรบูล แต่Observableฟังก์ชั่นการขุดอาจยังทำงานได้นานถึงหนึ่งวันและหลังจากนั้นในระหว่างการnextโทรก็จะทราบได้ว่าไม่มีการสมัครสมาชิกและต้องยกเลิก
Alex Che

76

ชั้นสมัครสมาชิกมีคุณสมบัติที่น่าสนใจ:

แสดงให้เห็นถึงทรัพยากรที่ใช้แล้วทิ้งเช่นการดำเนินการสังเกต การสมัครสมาชิกมีวิธีการหนึ่งที่สำคัญคือยกเลิกการสมัครสมาชิกซึ่งไม่มีข้อโต้แย้งใด ๆ และเพียงแค่กำจัดทรัพยากรที่จัดไว้ให้โดยการสมัครสมาชิก
นอกจากนี้การสมัครสมาชิกอาจถูกจัดกลุ่มเข้าด้วยกันผ่านวิธีการเพิ่ม () ซึ่งจะแนบการสมัครสมาชิกย่อยกับการสมัครสมาชิกปัจจุบัน เมื่อการสมัครสมาชิกถูกยกเลิกการเป็นสมาชิกเด็กทุกคน (และหลาน) จะถูกยกเลิกการสมัครเช่นกัน

คุณสามารถสร้างออบเจ็กต์การสมัครสมาชิกรวมที่จัดกลุ่มการสมัครสมาชิกทั้งหมดของคุณ คุณทำได้โดยสร้างการสมัครสมาชิกที่ว่างเปล่าและเพิ่มการสมัครสมาชิกโดยใช้add()วิธีการ เมื่อองค์ประกอบของคุณถูกทำลายคุณจะต้องยกเลิกการสมัครสมาชิกรวมเท่านั้น

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

ฉันใช้วิธีนี้ หากสงสัยว่าวิธีนี้ดีกว่าการใช้วิธีการกับ takeUntil () เช่นเดียวกับคำตอบที่ยอมรับ .. ข้อบกพร่อง?
Manuel Di Iorio

ไม่มีข้อเสียที่ฉันรู้ ฉันไม่คิดว่าจะดีไปกว่านี้
Steven Liekens

2
ดูmedium.com/@benlesh/rxjs-dont-unsubscribe-6753ed4fda87เพิ่มเติมสำหรับการอภิปรายอย่างเป็นทางการในวิธีการที่เมื่อเทียบกับวิธีการของการเก็บรวบรวมการสมัครสมาชิกและเรียกนี้takeUntil unsubscribe(วิธีนี้ดูเหมือนจะสะอาดกว่าฉันมาก)
Josh Kelley

3
ประโยชน์เล็ก ๆ น้อย ๆ ของคำตอบนี้: คุณไม่ต้องตรวจสอบว่าthis.subscriptionsเป็นโมฆะ
user2023861

1
เพียงหลีกเลี่ยงการผูกมัดวิธีการเพิ่มเช่นsub = subsciption.add(..).add(..)เพราะในหลายกรณีก็ก่อให้เกิดผลลัพธ์ที่ไม่คาดคิดgithub.com/ReactiveX/rxjs/issues/2769#issuecomment-345636477
Evgeniy Generalov

32

แนวปฏิบัติที่ดีที่สุดบางประการเกี่ยวกับการยกเลิกการติดตามที่สังเกตได้ภายในส่วนประกอบของ Angular:

คำพูดจาก Routing & Navigation

เมื่อสมัครรับข้อมูลที่สังเกตได้ในองค์ประกอบคุณเกือบจะจัดการยกเลิกการสมัครเมื่อส่วนประกอบถูกทำลาย

มีสิ่งที่น่าติดตามไม่กี่ที่ที่ไม่จำเป็น ActivatedRoute ที่สังเกตได้อยู่ในข้อยกเว้น

ActivatedRoute และสิ่งที่สังเกตได้นั้นเป็นฉนวนจากเราเตอร์เอง เราเตอร์จะทำลายส่วนประกอบที่กำหนดเส้นทางเมื่อไม่จำเป็นต้องใช้อีกต่อไปและ ActivatedRoute ที่ถูกฉีดจะตายพร้อมกับมัน

คุณสามารถยกเลิกการสมัครได้ฟรี มันไม่เป็นอันตรายและไม่เคยมีการปฏิบัติที่ไม่ดี

และในการตอบสนองต่อลิงค์ต่อไปนี้:

ฉันได้รวบรวมแนวปฏิบัติที่ดีที่สุดบางประการเกี่ยวกับการยกเลิกการสมัครสมาชิกที่น่าสังเกตภายในคอมโพเนนต์ Angular เพื่อแบ่งปันกับคุณ

  • httpการยกเลิกการติดตามที่สังเกตได้นั้นมีเงื่อนไขและเราควรพิจารณาถึงผลกระทบของการ 'เรียกกลับสมัคร' ที่ถูกเรียกใช้หลังจากส่วนประกอบถูกทำลายในแต่ละกรณี เรารู้ว่าการยกเลิกการสมัครและทำความสะอาดhttpที่สังเกตตัวเอง(1) , (2) ในขณะที่สิ่งนี้เป็นจริงจากมุมมองของทรัพยากรมันบอกเพียงครึ่งเรื่อง สมมติว่าเรากำลังพูดถึงการโทรโดยตรงhttpจากภายในองค์ประกอบและการhttpตอบสนองใช้เวลานานกว่าที่จำเป็นเพื่อให้ผู้ใช้ปิดส่วนประกอบ subscribe()ตัวจัดการจะยังคงถูกเรียกแม้ว่าส่วนประกอบถูกปิดและทำลาย สิ่งนี้อาจมีผลข้างเคียงที่ไม่พึงประสงค์และในสถานการณ์ที่เลวร้ายกว่าทำให้สถานะแอปพลิเคชันแตกหัก นอกจากนี้ยังสามารถทำให้เกิดข้อยกเว้นหากรหัสในการติดต่อกลับพยายามที่จะเรียกสิ่งที่เพิ่งถูกกำจัด อย่างไรก็ตามในเวลาเดียวกันพวกเขาเป็นที่ต้องการ เช่นสมมติว่าคุณกำลังสร้างไคลเอนต์อีเมลและคุณส่งเสียงเมื่ออีเมลส่งเสร็จแล้ว - คุณยังต้องการให้เกิดขึ้นแม้ว่าองค์ประกอบจะปิด ( 8 )
  • ไม่จำเป็นต้องยกเลิกการสมัครรับข้อมูลที่สมบูรณ์หรือผิดพลาด อย่างไรก็ตามไม่มีอันตรายใด ๆ ในการทำเช่นนั้น (7)
  • ใช้AsyncPipeให้มากที่สุดเนื่องจากจะเป็นการยกเลิกการสมัครสมาชิกโดยอัตโนมัติจากสิ่งที่สังเกตได้จากการทำลายส่วนประกอบ
  • ยกเลิกการสมัครจากสิ่งที่ActivatedRouteสังเกตได้เช่นroute.paramsถ้าพวกเขาสมัครเป็นสมาชิกในซ้อนกัน (เพิ่มใน tpl ด้วยตัวเลือกส่วนประกอบ) หรือองค์ประกอบแบบไดนามิกที่พวกเขาอาจจะสมัครสมาชิกหลายครั้งตราบใดที่องค์ประกอบหลัก / โฮสต์ที่มีอยู่ ไม่จำเป็นต้องยกเลิกการสมัครจากพวกเขาในสถานการณ์อื่น ๆ ตามที่ระบุไว้ในใบเสนอราคาข้างต้นจากRouting & Navigationเอกสาร
  • ยกเลิกการสมัครรับข้อมูลจากส่วนกลางที่ใช้ร่วมกันระหว่างส่วนประกอบที่เปิดเผยผ่านบริการเชิงมุมเช่นอาจมีการสมัครสมาชิกหลายครั้งตราบใดที่องค์ประกอบนั้นเริ่มต้นขึ้น
  • ไม่จำเป็นต้องยกเลิกการสมัครรับข้อมูลภายในของบริการขอบเขตของแอปพลิเคชันเนื่องจากบริการนี้ไม่เคยถูกทำลายเว้นแต่ว่าแอปพลิเคชันทั้งหมดของคุณถูกทำลายไม่มีเหตุผลที่แท้จริงที่จะยกเลิกการสมัครรับข้อมูลและไม่มีโอกาสรั่วไหล (6)

    หมายเหตุ:เกี่ยวกับบริการที่กำหนดขอบเขตเช่นผู้ให้บริการส่วนประกอบพวกเขาจะถูกทำลายเมื่อส่วนประกอบถูกทำลาย ในกรณีนี้หากเราสมัครรับข้อมูลใด ๆ ที่สามารถสังเกตได้ภายในผู้ให้บริการนี้เราควรพิจารณายกเลิกการสมัครรับข้อมูลโดยใช้OnDestroyเบ็ดวงจรชีวิตซึ่งจะถูกเรียกเมื่อบริการถูกทำลายตามเอกสาร
  • ใช้เทคนิคนามธรรมเพื่อหลีกเลี่ยงความยุ่งเหยิงของรหัสใด ๆ ที่อาจเกิดจากการยกเลิกการสมัคร คุณสามารถจัดการการสมัครของคุณด้วยtakeUntil (3)หรือคุณสามารถใช้npm แพ็คเกจนี้ที่กล่าวถึงที่(4) วิธีที่ง่ายที่สุดในการยกเลิกการสมัครรับข้อมูลจาก Observables in Angularในเชิงมุม
  • ยกเลิกการสมัครรับข้อมูลจากการFormGroupสังเกตอย่างเช่นform.valueChangesและform.statusChanges
  • ยกเลิกการสมัครRenderer2รับบริการที่สังเกตได้เสมอเช่นrenderer2.listen
  • ยกเลิกการสมัครสมาชิกจากทุกสิ่งที่สังเกตได้ว่าเป็นขั้นตอนการป้องกันการรั่วไหลของหน่วยความจำจนกระทั่งเอกสารเชิงมุมบอกเราอย่างชัดเจนว่าสิ่งที่ผู้สังเกตการณ์ไม่จำเป็นต้องยกเลิกการสมัคร (ตรวจสอบปัญหา: (5) เอกสารสำหรับ RxJS )
  • โบนัส: ใช้วิธีเชิงมุมเพื่อผูกเหตุการณ์เช่นเดียวHostListenerกับมุมที่สนใจเกี่ยวกับการลบตัวฟังเหตุการณ์หากจำเป็นและป้องกันการรั่วไหลของหน่วยความจำที่อาจเกิดขึ้นเนื่องจากการผูกเหตุการณ์

เคล็ดลับสุดท้ายที่ดี : หากคุณไม่ทราบว่ามีการยกเลิกการติดตาม / เสร็จสิ้นโดยอัตโนมัติหรือไม่ให้เพิ่มการcompleteโทรกลับไปที่subscribe(...)และตรวจสอบว่าได้รับการเรียกเมื่อองค์ประกอบถูกทำลาย


คำตอบสำหรับข้อ 6 นั้นไม่ถูกต้องนัก บริการจะถูกทำลายและngOnDestroyถูกเรียกใช้เมื่อมีการให้บริการในระดับอื่นนอกเหนือจากระดับรูทเช่นมีการให้บริการอย่างชัดเจนในองค์ประกอบที่จะถูกนำออกในภายหลัง ในกรณีเหล่านี้คุณควรยกเลิกการสมัครรับข้อมูลภายในบริการ
Drenai

@Drenai ขอบคุณสำหรับความคิดเห็นของคุณและฉันไม่เห็นด้วยอย่างสุภาพ หากส่วนประกอบถูกทำลายส่วนประกอบบริการและสิ่งที่สังเกตได้จะเป็น GCed ทั้งหมดและการยกเลิกการสมัครจะไม่มีประโยชน์ในกรณีนี้เว้นแต่คุณจะเก็บข้อมูลอ้างอิงสำหรับสิ่งที่สังเกตได้จากส่วนประกอบนั้น ๆ แม้จะมีการกำหนดขอบเขตการให้บริการแก่คอมโพเนนต์)
Mouneer

หากบริการที่ถูกทำลายมีการสมัครรับข้อมูลที่สามารถสังเกตได้ซึ่งอยู่ในบริการอื่นที่สูงกว่าในลำดับชั้น DI ดังนั้น GC จะไม่เกิดขึ้น หลีกเลี่ยงสถานการณ์นี้โดยยกเลิกการสมัครngOnDestroyซึ่งมักจะถูกเรียกเมื่อบริการถูกทำลายgithub.com/angular/angular/commit/ …
Drenai

1
@Drenai ตรวจสอบคำตอบที่ปรับปรุงแล้ว
Mouneer

2
@Tim ก่อนอื่นFeel free to unsubscribe anyway. It is harmless and never a bad practice.และเกี่ยวกับคำถามของคุณมันขึ้นอยู่กับ หากองค์ประกอบย่อยเริ่มต้นหลายครั้ง (ตัวอย่างเช่นเพิ่มภายในngIfหรือโหลดแบบไดนามิก) คุณจะต้องยกเลิกการเป็นสมาชิกเพื่อหลีกเลี่ยงการเพิ่มการสมัครสมาชิกหลายรายการในผู้สังเกตการณ์คนเดียวกัน มิฉะนั้นไม่จำเป็น แต่ฉันชอบที่จะยกเลิกการเป็นสมาชิกภายในองค์ประกอบของเด็กเพราะจะทำให้สามารถนำมาใช้ซ้ำได้มากขึ้นและแยกได้จากวิธีการใช้งาน
Mouneer

18

มันขึ้นอยู่กับ. หากการโทรsomeObservable.subscribe()คุณเริ่มต้นถือครองทรัพยากรบางอย่างที่ต้องถูกปลดปล่อยด้วยตนเองเมื่อครบวงจรชีวิตของส่วนประกอบของคุณคุณควรโทรtheSubscription.unsubscribe()เพื่อป้องกันหน่วยความจำรั่ว

ลองมาดูตัวอย่างของคุณให้ละเอียดยิ่งขึ้น:

getHero()http.get()ผลตอบแทนของ หากคุณมองเข้าไปในซอร์สโค้ดเชิงมุม 2 ให้http.get()สร้างตัวรับเหตุการณ์สองเหตุการณ์:

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

และโดยการโทรunsubscribe()คุณสามารถยกเลิกคำขอเช่นเดียวกับผู้ฟัง:

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

โปรดทราบว่า_xhrเป็นแพลตฟอร์มเฉพาะ แต่ฉันคิดว่ามันปลอดภัยที่จะถือว่าเป็นXMLHttpRequest()กรณีของคุณ

โดยปกตินี่เป็นหลักฐานเพียงพอที่จะรับประกันการunsubscribe()โทรด้วยตนเอง แต่ตามนี้ข้อมูลจำเพาะ WHATWGที่XMLHttpRequest()อยู่ภายใต้การเก็บขยะเมื่อมีการ "ทำ" แม้ว่าจะมีผู้ฟังเหตุการณ์ที่แนบมากับมัน ดังนั้นฉันคิดว่านั่นเป็นเหตุผลว่าทำไมคู่มือผู้ใช้อย่างเป็นทางการเชิงมุม 2 unsubscribe()และไม่ยอมให้ GC ทำความสะอาดผู้ฟัง

paramsในฐานะที่เป็นตัวอย่างที่สองของคุณก็ขึ้นอยู่กับการดำเนินงานของ ณ paramsวันนี้คู่มืออย่างเป็นทางการเชิงมุมไม่แสดงให้เห็นอีกต่อไปยกเลิกจาก ผมมองเข้าไปsrcอีกครั้งและพบว่าparamsเป็นเพียงBehaviorSubject unsubscribe()เนื่องจากไม่มีผู้ฟังเหตุการณ์หรือการจับเวลาถูกนำมาใช้และไม่มีตัวแปรทั่วโลกถูกสร้างขึ้นก็ควรจะปลอดภัยในการงด

บรรทัดล่างสำหรับคำถามของคุณคือมักจะเรียกunsubscribe()ว่าเป็นตัวป้องกันการรั่วไหลของหน่วยความจำเว้นแต่คุณจะแน่ใจว่าการดำเนินการของสิ่งที่สังเกตได้ไม่ได้สร้างตัวแปรทั่วโลกเพิ่มตัวฟังเหตุการณ์ตั้งค่าตัวจับเวลาหรือทำอะไรก็ได้ที่ทำให้หน่วยความจำรั่ว .

เมื่อมีข้อสงสัยให้ดูการใช้งานของสิ่งที่สังเกตได้ ถ้าสังเกตได้เขียนตรรกะบางสะอาดเป็นของซึ่งมักจะฟังก์ชั่นที่ถูกส่งกลับโดยสร้างแล้วคุณมีเหตุผลที่ดีที่จะพิจารณาอย่างจริงจังเรียกunsubscribe()unsubscribe()


6

เอกสารอย่างเป็นทางการของ Angular 2 ให้คำอธิบายเกี่ยวกับเวลาในการยกเลิกการสมัครและเมื่อใดที่สามารถเพิกเฉยได้อย่างปลอดภัย ดูลิงค์นี้:

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

ค้นหาย่อหน้าด้วยหัวเรื่องพ่อแม่และลูกสื่อสารผ่านบริการจากนั้นเลือกกล่องสีน้ำเงิน:

โปรดสังเกตว่าเราจับการบอกรับเป็นสมาชิกและยกเลิกการสมัครเมื่อ AstronautComponent ถูกทำลาย นี่เป็นขั้นตอนป้องกันหน่วยความจำรั่ว ไม่มีความเสี่ยงที่แท้จริงในแอพนี้เพราะอายุการใช้งานของ AstronautComponent เหมือนกับอายุการใช้งานของแอปเอง ซึ่งอาจไม่เป็นจริงในแอปพลิเคชันที่ซับซ้อนกว่านี้เสมอไป

เราไม่เพิ่มยามนี้ให้กับ MissionControlComponent เพราะในฐานะผู้ปกครองนั้นจะควบคุมอายุการใช้งานของ MissionService

ฉันหวังว่านี่จะช่วยคุณได้


3
ในฐานะที่เป็นองค์ประกอบที่คุณไม่เคยรู้ว่าคุณเป็นเด็กหรือไม่ ดังนั้นคุณควรยกเลิกการสมัครรับข้อมูลจากการสมัครเป็นแนวทางปฏิบัติที่ดีที่สุดเสมอ
SeriousM

1
จุดเกี่ยวกับ MissionControlComponent ไม่ได้เกี่ยวกับว่ามันเป็นผู้ปกครองหรือไม่มันเป็นองค์ประกอบที่ตัวเองให้บริการ เมื่อ MissionControl ถูกทำลายบริการและการอ้างอิงใด ๆ กับอินสแตนซ์ของบริการจึงไม่เกิดการรั่วไหล
สิ้นสุด

6

อ้างอิงจาก: การใช้การสืบทอดคลาสเพื่อเชื่อมต่อกับวงจรชีวิตของคอมโพเนนต์ Angular 2

อีกวิธีการทั่วไป:

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
  }

}

และใช้:

@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
สิ่งนี้ทำงานไม่ถูกต้อง โปรดใช้ความระมัดระวังเมื่อใช้โซลูชันนี้ คุณไม่มีthis.componentDestroyed$.next()สายเช่นวิธีที่ได้รับการยอมรับโดย sean ด้านบน ...
philn

4

คำตอบการแก้ไข # 3 อย่างเป็นทางการ (และรูปแบบต่างๆ) ทำงานได้ดี แต่สิ่งที่ทำให้ฉันได้รับคือ 'ความสับสน' ของตรรกะทางธุรกิจรอบ ๆ การสมัครรับข้อมูลที่สังเกตได้

นี่เป็นอีกวิธีการหนึ่งโดยใช้ wrappers

Warining:รหัสการทดลอง

ไฟล์subscribeAndGuard.tsจะใช้ในการสร้างส่วนขยายใหม่สังเกตการห่อและอยู่ภายในห่อ.subscribe() การใช้งานเป็นเช่นเดียวกับยกเว้นพารามิเตอร์แรกเพิ่มเติมอ้างอิงองค์ประกอบ ngOnDestroy()
.subscribe()

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

นี่คือส่วนประกอบที่มีการสมัครสมาชิกสองรายการส่วนที่ห่อหุ้มและอีกรายการที่ไม่มี ข้อแม้เดียวคือมันจะต้องใช้ OnDestroy (ที่มีเนื้อหาว่างเปล่าหากต้องการ) มิฉะนั้น Angular จะไม่ทราบว่าจะเรียกเวอร์ชันที่ถูกรวม

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

ผู้สาธิตพลั่วอยู่ที่นี่

หมายเหตุเพิ่มเติม: แก้ไขใหม่ 3 - โซลูชัน 'เป็นทางการ' ซึ่งสามารถทำให้ง่ายขึ้นโดยใช้ takeWhile () แทนที่จะใช้ takeUntil () ก่อนการสมัครสมาชิกและบูลีนแบบง่าย ๆ มากกว่าแบบสังเกตได้ใน 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

เนื่องจากวิธีการแก้ปัญหาของ seangwright (แก้ไข 3) ดูเหมือนจะมีประโยชน์มากฉันจึงพบว่ามันเจ็บปวดที่จะบรรจุคุณลักษณะนี้ไว้ในองค์ประกอบพื้นฐาน

คำตอบนี้ให้วิธีตั้งค่าฟรีจาก super call และทำให้ "componentDestroyed $" เป็นแกนหลักขององค์ประกอบพื้นฐาน

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

และจากนั้นคุณสามารถใช้คุณสมบัตินี้ได้อย่างอิสระเช่น:

@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

ตามคำตอบของ@seangwrightฉันได้เขียนคลาสนามธรรมที่จัดการการสมัครสมาชิกของ "infinite" observables ในส่วนประกอบ:

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

หากต้องการใช้เพียงขยายในองค์ประกอบเชิงมุมของคุณและเรียกsubscribe()วิธีการดังต่อไปนี้:

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

นอกจากนี้ยังยอมรับข้อผิดพลาดและดำเนินการเรียกกลับให้เสร็จสมบูรณ์ตามปกติวัตถุผู้สังเกตการณ์หรือไม่เรียกกลับเลย อย่าลืมโทรsuper.ngOnDestroy()ถ้าคุณกำลังใช้วิธีการนั้นในองค์ประกอบลูก

พบว่าที่นี่อ้างอิงเพิ่มเติมโดยเบนเลช: RxJS: อย่ายกเลิกการเป็นสมาชิก


2

ฉันลองวิธีการแก้ปัญหาของ seangwright (แก้ไข 3)

ไม่ทำงานสำหรับ Observable ที่สร้างขึ้นโดยตัวจับเวลาหรือช่วงเวลา

อย่างไรก็ตามฉันได้ทำงานโดยใช้วิธีอื่น:

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

ผมชอบสองคำตอบที่ผ่านมา แต่ผมมีประสบการณ์ปัญหาถ้า subclass ที่อ้างถึง"this"ในngOnDestroyใน

ฉันแก้ไขมันให้เป็นแบบนี้และดูเหมือนว่าจะสามารถแก้ไขปัญหานั้นได้

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

คุณต้องใช้ฟังก์ชั่นลูกศรเพื่อผูก 'this':this.ngOnDestroy = () => { f.bind(this)(); this.componentDestroyed$.complete(); };
Damsorian

2

ในกรณีที่จำเป็นต้องยกเลิกการเป็นสมาชิกสามารถใช้โอเปอเรเตอร์ต่อไปนี้สำหรับวิธีสังเกตได้

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

มันสามารถใช้เช่นนี้:

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

ผู้ประกอบการล้อมวิธี ngOnDestroy ขององค์ประกอบ

สำคัญ: ผู้ปฏิบัติงานควรเป็นคนสุดท้ายในท่อที่สังเกตได้


สิ่งนี้ใช้งานได้ดี แต่การอัปเกรดเป็นเหลี่ยม 9 ดูเหมือนว่าจะฆ่ามัน ใครรู้ว่าทำไม
ymerej

1

โดยปกติคุณจะต้องยกเลิกการสมัครเมื่อส่วนประกอบถูกทำลาย แต่ Angular จะจัดการกับมันมากขึ้นเรื่อย ๆ เช่นใน Angular4 รุ่นรองใหม่พวกเขามีส่วนนี้สำหรับการยกเลิกการสมัคร:

คุณต้องการยกเลิกการสมัครหรือไม่

ตามที่อธิบายไว้ใน ActivatedRoute: ส่วนหนึ่งข้อมูลร้านค้าสำหรับเส้นทางข้อมูลของหน้าการกำหนดเส้นทางและการนำทางเราเตอร์จะจัดการสิ่งที่สังเกตได้ที่มีให้และทำให้การสมัครสมาชิกเป็นภาษาท้องถิ่น การสมัครสมาชิกจะถูกทำความสะอาดเมื่อส่วนประกอบถูกทำลายป้องกันการรั่วไหลของหน่วยความจำดังนั้นคุณไม่จำเป็นต้องยกเลิกการสมัครจากเส้นทาง paramMap ที่สังเกตได้

นอกจากนี้ตัวอย่างด้านล่างเป็นตัวอย่างที่ดีจาก Angular เพื่อสร้างส่วนประกอบและทำลายมันหลังจากดูที่ส่วนประกอบดำเนิน OnDestroy ถ้าคุณต้องการ onInit คุณยังสามารถนำไปใช้ในองค์ประกอบของคุณเช่นการดำเนินการ 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
สับสน. คุณกำลังพูดอะไรที่นี่ คุณ (เอกสาร / บันทึกล่าสุดของ Angular) ดูเหมือนว่า Angular ดูแลและจากนั้นในภายหลังเพื่อยืนยันว่าการยกเลิกการสมัครเป็นรูปแบบที่ดี ขอบคุณ
jamie

1

อีกสั้น ๆ นอกเหนือจากสถานการณ์ดังกล่าวข้างต้นคือ:

  • ยกเลิกการสมัครเสมอเมื่อไม่มีค่าใหม่ในสตรีมที่สมัครเป็นสมาชิกหรือไม่สำคัญก็จะส่งผลให้จำนวนทริกเกอร์น้อยลงและเพิ่มประสิทธิภาพในบางกรณี กรณีเช่นส่วนประกอบที่ไม่มีข้อมูล / เหตุการณ์ที่สมัครเป็นสมาชิกหรือการสมัครสมาชิกใหม่ให้กับสตรีมใหม่ทั้งหมด (รีเฟรช ฯลฯ ) เป็นตัวอย่างที่ดีสำหรับการยกเลิกการสมัคร

0

ในการประยุกต์ใช้สปาที่ngOnDestroyฟังก์ชั่น (วงจรชีวิตเชิงมุม) สำหรับแต่ละสมัครคุณจำเป็นต้องยกเลิกการสมัครรับมัน advantage => เพื่อป้องกันไม่ให้สถานะหนักเกินไป

ตัวอย่างเช่น: ใน component1:

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

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

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

อยู่ในการให้บริการ:

import {BehaviorSubject} from 'rxjs/BehaviorSubject';

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

ใน component2:

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

สำหรับการจัดการการสมัครฉันใช้คลาส "Unsubscriber"

นี่คือ Unsubscriber Class

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

และคุณสามารถใช้คลาสนี้ในองค์ประกอบ / บริการ / เอฟเฟกต์อื่น ๆ

ตัวอย่าง:

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

    this.addSubscription(subscription);
}

0

คุณสามารถใช้Subscriptionคลาสล่าสุดเพื่อยกเลิกการสมัคร Observable ด้วยรหัสที่ไม่ยุ่ง

เราสามารถทำได้ด้วยnormal variableแต่มันจะเป็นoverride the last subscriptionในการสมัครใหม่ทุกครั้งเพื่อหลีกเลี่ยงปัญหานี้และวิธีการนี้มีประโยชน์มากเมื่อคุณติดต่อกับ Obseravables จำนวนมากขึ้นและประเภทของ Obeservables ที่ชอบBehavoiurSubjectและSubject

การสมัครสมาชิก

แสดงให้เห็นถึงทรัพยากรที่ใช้แล้วทิ้งเช่นการดำเนินการสังเกต การสมัครสมาชิกมีวิธีการหนึ่งที่สำคัญคือยกเลิกการสมัครสมาชิกโดยไม่มีการโต้แย้งและเพียงแค่กำจัดทรัพยากรที่จัดไว้ให้โดยการสมัครสมาชิก

คุณสามารถใช้สิ่งนี้ได้สองวิธี

  • คุณสามารถผลักดันการสมัครสมาชิกไปยังอาร์เรย์การสมัครสมาชิกโดยตรง

     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());
      }
    
  • ใช้add()ของSubscription

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

A Subscriptionสามารถระงับการสมัครรับข้อมูลย่อยและยกเลิกการสมัครทั้งหมดได้อย่างปลอดภัย วิธีนี้จะจัดการกับข้อผิดพลาดที่เป็นไปได้ (เช่นถ้าการสมัครสมาชิกย่อยใด ๆ เป็นโมฆะ)

หวังว่านี่จะช่วย .. :)


0

แพคเกจ SubSink เป็นทางออกที่ง่ายและสอดคล้องกันสำหรับการยกเลิกการสมัคร

ในฐานะที่เป็นคนอื่นได้กล่าวว่าผมต้องการที่จะขอแนะนำแพคเกจ Subsink ที่สร้างขึ้นโดยวอร์ดเบลล์: https://github.com/wardbell/subsink#readme

ฉันใช้มันในโครงการเพราะเราเป็นนักพัฒนาหลายคนที่ใช้มัน มันช่วยได้มากในการมีวิธีการทำงานที่สอดคล้องกันในทุกสถานการณ์


0

สำหรับสิ่งที่น่าสังเกตที่สมบูรณ์โดยตรงหลังจากที่ปล่อยผลลัพธ์เช่นAsyncSubjectหรือตัวอย่างที่สามารถสังเกตได้จากคำร้องขอ http และคุณไม่จำเป็นต้องยกเลิกการสมัคร มันไม่เจ็บที่จะเรียกร้องสิ่งunsubscribe()เหล่านั้น แต่ถ้าสิ่งที่สังเกตได้คือclosedวิธีการยกเลิกการเป็นสมาชิกจะไม่ทำอะไรเลย :

if (this.closed) {
  return;
}

เมื่อคุณมีนักสังเกตการณ์ระยะยาวที่ปล่อยค่าหลายค่าในช่วงเวลาหนึ่ง (เช่นตัวอย่าง a BehaviorSubjectหรือ a ReplaySubject) คุณต้องยกเลิกการสมัครสมาชิกเพื่อป้องกันการรั่วไหลของหน่วยความจำ

คุณสามารถสร้างสิ่งที่สังเกตได้ซึ่งทำให้เสร็จสมบูรณ์โดยตรงหลังจากที่ปล่อยผลลัพธ์จากสิ่งที่สังเกตได้ที่ยาวนานเช่นนี้โดยใช้โอเปอเรเตอร์ ในบางคำตอบที่นี่take(1)มีการกล่าวถึงท่อ แต่ฉันชอบท่อ ความแตกต่างคือมันจะ:first()take(1)

ส่งการEmptyErrorโทรกลับข้อผิดพลาดของผู้สังเกตการณ์หากการสังเกตเสร็จสมบูรณ์ก่อนที่จะมีการส่งการแจ้งเตือนครั้งต่อไป

ข้อดีอีกอย่างของไพพ์แรกคือคุณสามารถผ่านเพรดิเคตที่จะช่วยให้คุณคืนค่าแรกที่เป็นไปตามเกณฑ์ที่กำหนด:

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

อันดับแรกจะเสร็จสมบูรณ์ทันทีหลังจากส่งค่าแรก (หรือเมื่อผ่านการโต้แย้งฟังก์ชันค่าแรกที่ตรงตามคำกริยาของคุณ) ดังนั้นจึงไม่จำเป็นต้องยกเลิกการสมัคร

บางครั้งคุณไม่แน่ใจว่าคุณมีชีวิตที่ยืนยาวได้หรือไม่ ฉันไม่ได้บอกว่าเป็นการฝึกฝนที่ดี แต่คุณสามารถเพิ่มfirstไปป์ไลน์ได้เสมอเพื่อให้แน่ใจว่าคุณไม่จำเป็นต้องยกเลิกการสมัครด้วยตนเอง การเพิ่มfirstไปป์เพิ่มเติมบนสิ่งที่สังเกตได้ซึ่งจะเปล่งเพียงหนึ่งค่าไม่ทำให้เกิดความเสียหาย

ในระหว่างการพัฒนาคุณสามารถใช้ท่อที่จะล้มเหลวหากแหล่งที่มาส่งเสียงสังเกตหลายเหตุการณ์ที่เกิดขึ้น วิธีนี้จะช่วยให้คุณสำรวจประเภทของสิ่งที่สังเกตได้และจำเป็นต้องยกเลิกการเป็นสมาชิกหรือไม่single

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

firstและsingleดูเหมือนคล้ายกันมากทั้งท่อสามารถใช้กริยาตัวเลือกที่แตกต่างกัน แต่มีความสำคัญและสรุปอย่างในคำตอบ StackOverflow นี้ได้ที่นี่ :

เป็นครั้งแรก

จะปล่อยออกมาทันทีที่รายการแรกปรากฏขึ้น จะเสร็จสมบูรณ์ทันทีหลังจากนั้น

เดียว

จะล้มเหลวหากแหล่งที่สังเกตได้ส่งเสียงหลายเหตุการณ์


หมายเหตุ ฉันพยายามให้ถูกต้องและครบถ้วนที่สุดเท่าที่จะเป็นไปได้ในคำตอบของฉันโดยอ้างอิงถึงเอกสารอย่างเป็นทางการ แต่โปรดแสดงความคิดเห็นหากสิ่งที่สำคัญขาดหายไป ...


-1

--- อัปเดตโซลูชัน Angular 9 และ Rxjs 6

  1. การใช้งานunsubscribeที่ngDestroyวงจรชีวิตของ Angular Component
class SampleComponent implements OnInit, OnDestroy {
  private subscriptions: Subscription;
  private sampleObservable$: Observable<any>;

  constructor () {}

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

  ngOnDestroy() {
    this.subscriptions.unsubscribe();
  }
}
  1. ใช้takeUntilใน 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. สำหรับการกระทำบางอย่างที่คุณเรียกngOnInitว่าจะเกิดขึ้นเพียงครั้งเดียวเมื่อคอมโพเนนต์เริ่มต้น
class SampleComponent implements OnInit {

  private sampleObservable$: Observable<any>;

  constructor () {}

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

เรายังมีasyncท่อ แต่อันนี้ใช้ในแม่แบบ (ไม่ได้อยู่ในองค์ประกอบเชิงมุม)


ตัวอย่างแรกของคุณไม่สมบูรณ์
Manole Paul-Sebastian
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.