簡體   English   中英

RxJS: takeUntil() Angular 組件的 ngOnDestroy()

[英]RxJS: takeUntil() Angular component's ngOnDestroy()

tl;dr:基本上我想將 Angular 的ngOnDestroy與 Rxjs takeUntil()運算符結合起來。 - 那可能嗎?

我有一個 Angular 組件,可以打開多個 Rxjs 訂閱。 這些需要在組件銷毀時關閉。

一個簡單的解決方案是:

class myComponent {

  private subscriptionA;
  private subscriptionB;
  private subscriptionC;

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    this.subscriptionA = this.serviceA.subscribe(...);
    this.subscriptionB = this.serviceB.subscribe(...);
    this.subscriptionC = this.serviceC.subscribe(...);
  }

  ngOnDestroy() {
    this.subscriptionA.unsubscribe();
    this.subscriptionB.unsubscribe();
    this.subscriptionC.unsubscribe();
  }

}

這可行,但有點多余。 我特別不喜歡那樣—— unsubscribe()在別的地方,所以你要記住它們是鏈接的。 - 組件 state 被訂閱污染。

我更喜歡使用takeUntil()運算符或類似的東西,使它看起來像這樣:

class myComponent {

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    const destroy = Observable.fromEvent(???).first();
    this.subscriptionA = this.serviceA.subscribe(...).takeUntil(destroy);
    this.subscriptionB = this.serviceB.subscribe(...).takeUntil(destroy);
    this.subscriptionC = this.serviceC.subscribe(...).takeUntil(destroy);
  }

}

是否有銷毀事件或類似事件可以讓我使用takeUntil()或其他方式來簡化組件架構? 我意識到我可以在構造函數中自己創建一個事件或在ngOnDestroy()中觸發的事件,但這最終不會使事情變得更容易閱讀。

您可以ReplaySubject利用ReplaySubject

編輯:與 RxJS 6.x 不同:注意pipe()方法的使用。

class myComponent {
  private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC) {}

  ngOnInit() {
    this.serviceA
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
    this.serviceB
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
    this.serviceC
      .pipe(takeUntil(this.destroyed$))
      .subscribe(...);
  }

  ngOnDestroy() {
    this.destroyed$.next(true);
    this.destroyed$.complete();
  }
}

這僅對 RxJS 5.x 及更早版本有效:

class myComponentOld {
  private destroyed$: ReplaySubject<boolean> = new ReplaySubject(1);

  constructor(private serviceA: ServiceA) {}

  ngOnInit() {
    this.serviceA
      .takeUntil(this.destroyed$)
      .subscribe(...);
  }

  ngOnDestroy() {
    this.destroyed$.next(true);
    this.destroyed$.complete();
  }
}

使用 npm 包@w11k/ngx-componentdestroyed 中componentDestroyed()函數是迄今為止使用 takeUntil 的最簡單方法:

@Component({
  selector: 'foo',
  templateUrl: './foo.component.html'
})
export class FooComponent implements OnInit, OnDestroy {
  ngOnInit() {
    Observable.interval(1000)
      .takeUntil(componentDestroyed(this)) // <--- magic is here!
      .subscribe(console.log);
  }

  ngOnDestroy() {}
}

這是要直接包含在您的代碼中的componentDestroyed()版本:

// Based on https://www.npmjs.com/package/ng2-rx-componentdestroyed
import { OnDestroy } from '@angular/core';
import { ReplaySubject } from 'rxjs/ReplaySubject';

export function componentDestroyed(component: OnDestroy) {
  const oldNgOnDestroy = component.ngOnDestroy;
  const destroyed$ = new ReplaySubject<void>(1);
  component.ngOnDestroy = () => {
    oldNgOnDestroy.apply(component);
    destroyed$.next(undefined);
    destroyed$.complete();
  };
  return destroyed$;
}

好吧,這歸結為您關閉訂閱的意思。 基本上有兩種方法可以做到這一點:

  1. 使用完成鏈的運算符(例如takeWhile() )。
  2. 取消訂閱源 Observable。

很高興知道這兩個不一樣。

例如,當使用takeWhile()您讓操作員發送complete通知,並傳播給您的觀察者。 所以如果你定義:

...
.subscribe(..., ..., () => doWhatever());

然后當你用例如完成鏈時。 takeWhile() doWhatever()函數將被調用。

例如,它可能如下所示:

const Observable = Rx.Observable;
const Subject = Rx.Subject;

let source = Observable.timer(0, 1000);
let subject = new Subject();

source.takeUntil(subject).subscribe(null, null, () => console.log('complete 1'));
source.takeUntil(subject).subscribe(null, null, () => console.log('complete 2'));
source.takeUntil(subject).subscribe(null, null, () => console.log('complete 3'));

setTimeout(() => {
  subject.next();
}, 3000);

3 秒后將調用所有完整的回調。

另一方面,當您取消訂閱時,您表示您不再對源 Observable 生成的項目感興趣。 然而,這並不意味着源必須完成。 你只是不再關心了。

這意味着您可以從.subscribe(...)調用中收集所有Subscription並立即取消所有訂閱:

let subscriptions = new Rx.Subscription();
let source = Observable.timer(0, 1000);

subscriptions.add(source.subscribe(null, null, () => console.log('complete 1')));
subscriptions.add(source.subscribe(null, null, () => console.log('complete 2')));
subscriptions.add(source.subscribe(null, null, () => console.log('complete 3')));

setTimeout(() => {
  subscriptions.unsubscribe();
}, 3000);

現在,在 3 秒延遲之后,控制台不會打印任何內容,因為我們取消訂閱並且沒有調用完整的回調。

所以你想使用什么取決於你和你的用例。 請注意,取消訂閱與完成不同,即使我想在您的情況下這並不重要。

請在 TakeUntil 中使用多態性(2022 年 4 月 13 日)

如果您正在編寫protected destroy$ = new Subject<void>(); 在你制作的每個組件中,那么你應該問自己,“為什么我沒有遵循DRY(不要重復自己)原則?”

遵循 DRY 原則,創建一個抽象基礎組件(抽象類不能直接實例化)來處理你的銷毀信號。

@Component({ template: '' })
export abstract class BaseComponent extends Subscribable {
  // Don't let the outside world trigger this destroy signal.
  // It's only meant to be trigger by the component when destroyed! 
  private _destroy = new Subject<void>();
  public destroy$ = this._destroy as Observable<void>;
  /** Lifecycle hook called by angular framework when extended class dies. */
  ngOnDestroy(): void {
    this._destroy.next();
  }
}

做一個方便的擴展 function 來簡化事情。

declare module 'rxjs/internal/Observable' {
  interface Observable<T> {
    dieWith(comp: BaseComponent): Observable<T>;
  }
}

Observable.prototype.dieWith = function<T>(comp: BaseComponent): Observable<T> {
    return this.pipe(takeUntil(comp.destroy$));
};

每當您需要處理訂閱時,擴展您的 BaseComponent。

@Component({ ... })
export class myComponent extends BaseComponent {

  constructor(
    private serviceA: ServiceA,
    private serviceB: ServiceB,
    private serviceC: ServiceC
  ) {
    super();
  }

  ngOnInit() {
    this.serviceA.a$.dieWith(this).subscribe(...);
    this.serviceB.b$.dieWith(this).subscribe(...);
    this.serviceC.c$.dieWith(this).subscribe(...);
  }

}

您已經像專業人士一樣正式處理 Angular 組件中的訂閱。

您的同事稍后會感謝您!

編碼愉快!

如果您的組件直接綁定到路由,則可以通過使用帶takeUntil() Router事件來避免添加狀態。 這樣,一旦您離開該組件,它就會自動為您清理其訂閱。

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { MyService } from './my.service';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/takeUntil';

@Component({
    ...
})
export class ExampleComopnent implements OnInit {

    constructor(private router: Router, private myService: MyService) {
    }

    ngOnInit() {
        this.myService.methodA()
            .takeUntil(this.router.events)
            .subscribe(dataA => {
                ...
            });

        this.myService.methodB()
            .takeUntil(this.router.events)
            .subscribe(dataB => {
                ...
            });
    }
}

注意:這個簡單的例子沒有考慮受保護的路線或取消的路線導航。 如果有可能觸發其中一個路由器事件但路線導航被取消,您需要過濾路由器事件,以便在適當的點觸發它 - 例如,在Route Guard檢查之后或一旦導航已完成。

this.myService.methodA()
    .takeUntil(this.router.events.filter(e => e instanceOf NavigationEnd))
    .subscribe(dataA => {
        ...
    });

創建基類

import { Subject } from 'rxjs/Rx';
import { OnDestroy } from '@angular/core';

 export abstract class Base implements OnDestroy {

 protected componentDestroyed$: Subject<any>;

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

    const destroyFunc = this.ngOnDestroy;
    this.ngOnDestroy = () => {
        destroyFunc.bind(this)();
        this.componentDestroyed$.next();
        this.componentDestroyed$.complete();
    };
}
// placeholder of ngOnDestroy. no need to do super() call of extended class.
public ngOnDestroy() {
    // no-op
}

}

該組件將是,

擴展基類

export class Test extends Base{
}

當您訂閱時

service.takeUntil(this.componentDestroyed$
    .subscribe(...)

這是一個全局級別的更改,無論何時您要訂閱,請在整個項目中使用相同的方法。 在需要的任何更改中,您可以在基類中進行修改

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM