[angular] Angular / RxJs 언제 구독을 취소해야합니까?

NgOnDestroy 수명주기 동안 언제 Subscription인스턴스를 저장 하고 호출 해야 unsubscribe()하며 언제 무시할 수 있습니까?

모든 구독을 저장하면 구성 요소 코드에 많은 혼란이 발생합니다.

HTTP 클라이언트 가이드 는 다음과 같은 구독을 무시합니다.

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

동시에 Route & Navigation Guide 는 다음과 같이 말합니다.

결국 우리는 다른 곳을 탐색 할 것입니다. 라우터는 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();
}



답변

— 편집 4-추가 리소스 (2018/09/01)

Angular Ben Lesh와 Ward Bell 의 최근 모험 에피소드 에서 구성 요소를 구독 취소하는 방법 / 언제와 관련된 문제에 대해 설명합니다. 토론은 약 1:05:30에 시작됩니다.

와드 언급 right now there's an awful takeUntil dance that takes a lot of machinery과 샤이 레즈 닉 언급 Angular handles some of the subscriptions like http and routing.

이에 대한 응답으로 Ben은 Observables가 Angular 구성 요소 수명주기 이벤트에 연결될 수 있도록하기위한 논의가 있다고 언급했으며 Ward는 구성 요소 내부 상태로 유지되는 Observable을 완료 할시기를 알기위한 방법으로 구성 요소가 가입 할 수있는 Observable 수명주기 이벤트를 제안합니다.

즉, 현재 대부분 솔루션이 필요하므로 여기에 다른 리소스가 있습니다.

  1. takeUntil()RxJs 핵심 팀 멤버 Nicholas Jamieson 의 패턴에 대한 권장 사항 과이를 강화하는 데 도움이되는 tslint 규칙. https://ncjamieson.com/avoiding-takeuntil-leaks/

  2. 구성 요소 인스턴스 ( this)를 매개 변수로 사용하고 자동으로 구독을 취소 하는 Observable 연산자를 제공하는 경량 npm 패키지 ngOnDestroy.
    https://github.com/NetanelBasal/ngx-take-until-destroy

  3. AOT 빌드를하지 않으면 위의 또 다른 변형이 약간 더 인체 공학적입니다 (그러나 우리는 모두 AOT를해야합니다).
    https://github.com/smnbbrv/ngx-rx-collector

  4. *ngSubscribe비동기 파이프처럼 작동하지만 템플릿에 포함 된 뷰를 생성하여 템플릿 전체에서 ‘포장 해제’값을 참조 할 수있는 사용자 지정 지시문 .
    https://netbasal.com/diy-subscription-handling-directive-in-angular-c8f6e762697f

필자는 Nicholas의 블로그에 대한 과다 사용은 takeUntil()구성 요소가 너무 많은 노력을 기울이고 있고 기존 구성 요소를 FeaturePresentational 구성 요소로 분리해야한다는 신호일 수 있다고 언급했습니다 . 그런 다음 | asyncFeature 구성 요소 Input에서 Presentational 구성 요소로 Observable을 사용할 수 있습니다. 즉, 구독이 필요하지 않습니다. 이 방법에 대한 자세한 내용은 여기를 참조하십시오

— 편집 3- ‘공식적인’솔루션 (2017/04/09)

나는 NGConf 에서이 질문에 대해 Ward Bell과 이야기했다. (나는 그에게 그가 옳았다 고 대답했다. ). 그는 또한 다음 공식 추천으로 내 SO 답변을 업데이트 할 수 있다고 말했습니다.

앞으로 우리 모두가 사용해야 할 해결책은 클래스 코드 내에서 호출하는 private ngUnsubscribe = new Subject();모든 구성 요소에 필드를 추가하는 것 입니다 ..subscribe()Observable

우리는 전화를 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 tutorial의 Routing 장은 이제 다음과 같이 말합니다 : “라우터는 제공하는 옵저버 블을 관리하고 서브 스크립 션을 현지화합니다. 서브 스크립 션은 컴포넌트가 파괴 될 때 정리되어 메모리 누수를 방지하므로 구독을 취소 할 필요가 없습니다. 경로 매개 변수를 관찰 할 수 있습니다. ” – 마크 라지 콕

여기의 논의 워드 벨이의 모든 것을 해명이 작품에 언급 경우 라우터 Observable 인에 대한 각 문서에 대한 Github의 문제에가.

— 편집 1

소스 4

이러한면에서 NgEurope의 비디오 롭 Wormald 또한 라우터 Observables은 가입을 취소 할 필요가 없습니다 말했다. 그는 또한 언급 http서비스와 ActivatedRoute.params이에 2016년 11월에서 비디오를 .

— 원래 답변

TLDR :

이 질문에 대해 (2) 종류가있다 Observables유한 값과 무한 값.

http Observables생산 한정된 (1)의 값과 같은 것을 DOM은 event listener Observables생산 무한 값.

subscribe비동기 파이프를 사용하지 않고 수동으로 호출하면 infiniteunsubscribe 에서 호출 합니다. Observables

유한 한 것들 에 대해 걱정하지 마십시오 RxJs.

소스 1

나는 각도의 Gitter에서 롭 Wormald에서 답변을 추적 여기 .

그는 말한다 (명확하고 강조하기 위해 재구성되었다)

그 경우 단일 값 시퀀스 (HTTP 요청 등)를 수동으로 정리 불필요 (수동 컨트롤러에 가입 가정)

완료된 시퀀스 인 경우 “(단일 값 시퀀스, la http 중 하나)

그 무한 순서 경우 , 당신은 취소해야 비동기 파이프가 당신을 위해 수행하는

또한 Observables에 대한 이 YouTube 비디오 에서 they clean up after themselves… Observables 의 맥락에서 complete(Promises와 같이 항상 1 개의 가치를 창출하고 끝나기 때문에 완료되는 약속과 같이) xhr이벤트 를 정리하기 위해 Promises에서 구독을 취소하는 것에 대해 걱정하지 않습니다. 청취자, 그렇지?).

소스 2

또한 Angular 2Rangle 안내서에서 읽습니다.

대부분의 경우 조기 취소를 원하거나 Observable이 구독보다 수명이 길지 않으면 구독 취소 메소드를 명시 적으로 호출 할 필요가 없습니다. Observable 연산자의 기본 동작은 .complete () 또는 .error () 메시지가 게시되는 즉시 구독을 처리하는 것입니다. RxJS는 대부분 “화재와 잊어 버리기”방식으로 사용되도록 설계되었습니다.

문구는 언제 our Observable has a longer lifespan than our subscription적용됩니까?

구성 요소 내부에서 구독이 작성 될 때 적용되며 Observable완료 되기 전에 (또는 오래 전에는) 소멸 됩니다.

http10 개의 값을 방출 하는 요청 또는 옵저버 블에 가입 하고 http요청이 반환 되기 전에 구성 요소가 파괴 되거나 10 개의 값이 방출 된 경우에도 이것을 의미로 읽습니다 .

요청이 리턴되거나 10 번째 값이 최종적으로 방출 Observable되면 완료되고 모든 자원이 정리됩니다.

소스 3

우리 가 동일한 Rangle 안내서 에서이 예제 를 보면 Subscriptionto route.params가 필요할 unsubscribe()때 알 수 없기 때문에 to 가 필요 하다는 것을 알 수 있습니다 params(새 값 방출).

경로 매개 변수가 계속 변경 될 가능성이있는 경우 (앱이 종료 될 때까지 기술적으로 변경 될 수 있음) 탐색하지 않으면 구성 요소가 손상 될 수 있으며 구독에 할당 된 리소스는 아직 없기 때문에 할당 completion됩니다.


답변

구독을 많이하지 않아도되고 수동으로 구독을 취소 할 필요가 없습니다. 보스와 같은 구독을 처리 하려면 SubjecttakeUntil 콤보를 사용하십시오 .

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 이 제안한 대체 방법takeUntil 대신 takeWhile을 사용 합니다. 당신은 그것을 원할 수도 있지만, 이렇게하면 컴포넌트의 ngDestroy에서 Observable 실행이 취소되지 않습니다 (예를 들어, 시간이 많이 걸리는 계산을하거나 서버의 데이터를 기다릴 때). 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
  }
}


답변

Subscription 클래스에는 흥미로운 기능이 있습니다.

Observable의 실행과 같은 일회용 자원을 나타냅니다. 서브 스크립 션에는 인수를 취하지 않고 서브 스크립 션이 보유한 자원을 폐기하는 중요한 방법 인 구독 취소가 있습니다.
또한 서브 스크립 션은 add () 메소드를 통해 그룹화되어 하위 서브 스크립 션을 현재 서브 스크립 션에 첨부합니다. 구독을 구독 취소하면 모든 자식 (및 손자)도 구독 취소됩니다.

모든 구독을 그룹화하는 집계 구독 오브젝트를 작성할 수 있습니다. 빈 구독을 작성하고 해당 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();
  }
}


답변

Angular 구성 요소 내에서 옵저버 블 구독 취소에 대한 모범 사례 중 일부 :

의 인용 Routing & Navigation

컴포넌트에서 옵저버 블을 구독 할 때는 컴포넌트가 소멸 될 때 거의 항상 구독을 취소합니다.

이것이 필요하지 않은 예외적 인 관측 물이 몇 가지 있습니다. ActivatedRoute 옵저버 블은 예외입니다.

ActivatedRoute 및 해당 관찰 가능 항목은 라우터 자체와 절연되어 있습니다. 라우터는 더 이상 필요하지 않은 라우팅 된 구성 요소를 삭제하고 주입 된 ActivatedRoute가 함께 죽습니다.

어쨌든 구독을 취소하십시오. 무해하고 결코 나쁜 습관이 아닙니다.

그리고 다음 링크에 응답합니다.

Angular 구성 요소 내에서 관찰 가능한 구독 취소에 대한 모범 사례 중 일부를 수집하여 귀하와 공유했습니다.

  • http관찰 가능한 구독 취소는 조건부이므로 구성 요소가 사례별로 소멸 된 후에 실행되는 ‘구독 콜백’의 영향을 고려해야합니다. 우리는 각도가 http관찰 가능 객체를 탈퇴하고 청소한다는 것을 알고 있습니다 (1) , (2) . 이것은 자원의 관점에서 사실이지만, 이야기의 절반 만 말해줍니다. http구성 요소 내에서 직접 전화하는 것에 대해 이야기하고 http있으며 사용자가 구성 요소를 닫을 때 까지 응답 시간이 오래 걸렸습니다. 그만큼subscribe()구성 요소가 닫히고 파괴 된 경우에도 처리기는 계속 호출됩니다. 이로 인해 원치 않는 부작용이 생길 수 있으며 더 나쁜 시나리오에서는 응용 프로그램 상태가 손상됩니다. 콜백의 코드가 방금 폐기 된 것을 호출하려고하면 예외가 발생할 수 있습니다. 그러나 동시에 그들은 때때로 바람직하다. 예를 들어, 이메일 클라이언트를 작성 중이고 이메일 전송이 완료되면 소리가 울린다 고 가정 해 봅시다. 구성 요소가 닫혀 있어도 여전히 발생하기를 원할 것입니다 ( 8 ).
  • 완료되거나 오류가 발생한 옵저버 블의 구독을 취소 할 필요가 없습니다. 그러나 그렇게하는 데 아무런 해가 없습니다 (7) .
  • AsyncPipe구성 요소 삭제시 관찰 가능 항목을 자동으로 구독 해제하므로 가능한 한 많이 사용하십시오 .
  • 탈퇴에서 ActivatedRoute와 같은 관찰 가능한 route.params그들이 오랫동안 부모 / 호스트 구성 요소가 존재하는만큼을 가입 할 수있다 그들이 중첩 (구성 요소 선택과 TPL 내에서 추가) 또는 동적 구성 요소 내부에 가입하는 경우. 위의 인용문에서 언급 한 다른 시나리오에서는 문서에서 탈퇴 할 필요가 없습니다 Routing & Navigation.
  • Angular 서비스를 통해 노출되는 구성 요소간에 공유되는 전역 옵저버 블의 구독을 취소합니다 (예 : 구성 요소가 초기화되는 한 여러 번 구독 될 수 있음).
  • 이 서비스는 절대 파괴되지 않기 때문에 애플리케이션 범위 서비스의 내부 감시 대상을 구독 취소 할 필요가 없습니다. 전체 애플리케이션이 파괴되지 않는 한, 구독을 취소 할 실제 이유가 없으며 메모리 누수가 발생할 가능성이 없습니다. (6) .

    참고 : 범위가 지정된 서비스, 즉 구성 요소 공급자와 관련하여 구성 요소가 손상되면 해당 서비스가 삭제됩니다. 이 경우, 우리가이 제공자 내부의 Observable에 가입 ​​한 경우, OnDestroy문서에 따라 서비스가 파괴 될 때 호출되는 수명주기 후크를 사용하여 구독 취소를 고려해야 합니다.

  • 구독 취소로 인해 발생할 수있는 코드 혼잡을 피하려면 추상 기술을 사용하십시오. takeUntil (3)으로 구독을 관리 하거나 (4) Angular의 Observables 구독을 취소하는 가장 쉬운 방법에 언급 된 이 npm 패키지를 사용할 수 있습니다 .
  • 항상 구독을 취소 FormGroup같은 관찰 가능한 form.valueChangesform.statusChanges
  • 다음 Renderer2과 같은 서비스 옵저버 블을 항상 구독 취소하십시오.renderer2.listen
  • Angular Docs가 어떤 옵저버 블을 구독 해제 할 필요가 없는지 명시 적으로 알려줄 때까지 모든 옵저버 블 에서 메모리 누수 가드 단계로 구독을 취소하십시오 (문제 확인 : (5) RxJS Unsubscribing에 대한 문서 (Open) ).
  • 보너스 : HostListener필요한 경우 이벤트 리스너를 제거하는 데주의를 기울이고 이벤트 바인딩으로 인한 잠재적 인 메모리 누수를 방지 하기 위해 항상 각도 방법을 사용하여 각도와 같은 이벤트를 바인드하십시오.

좋은 최종 팁 : Observable이 자동으로 구독 취소 / 완료되는지 여부를 모르는 경우 complete콜백을 추가 subscribe(...)하고 구성 요소가 손상 될 때 호출되는지 확인하십시오.


답변

때에 따라 다르지. 을 호출 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()있습니다.


답변

Angular 2 공식 문서는 구독을 취소 할 때와 안전하게 무시할 수있는시기에 대한 설명을 제공합니다. 이 링크를 살펴보십시오.

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

부모와 자녀가 서비스를 통해 통신 한 다음 파란색 상자 로 표시된 단락을 찾습니다 .

AstronautComponent가 파괴되면 구독을 캡처하고 구독을 취소합니다. 메모리 누수 가드 단계입니다. AstronautComponent의 수명은 앱 자체의 수명과 동일하므로이 앱에는 실제 위험이 없습니다. 더 복잡한 응용 프로그램에서는 항상 그렇지는 않습니다.

부모로서 MissionService의 수명을 제어하기 때문에이 Guard를 MissionControlComponent에 추가하지 않습니다.

이것이 도움이되기를 바랍니다.


답변

기반 : 클래스 상속을 사용하여 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));
    }
}