在RxJS 6中重置ReplaySubject

时间:2018-07-03 01:56:12

标签: javascript rxjs

我有一个可过滤的“活动日志”,该日志当前使用ReplaySubject实现(因为一些组件正在使用它,并且它们可能在不同的时间订阅)。

当用户更改过滤器设置时,会发出一个新请求,但是结果会附加到ReplaySubject上,而不是替换它。

我想知道是否有更新ReplaySubject以便仅使用switchMap之类的东西发送新项目的消息?

否则,我可能需要使用返回所有活动条目数组的BehaviorSubject或重新创建ReplaySubject并通知用户(可能通过使用另一个可观察的对象)来取消订阅并重新订阅新可观察到的。

4 个答案:

答案 0 :(得分:5)

如果您希望能够在不让其订阅者明确取消订阅和重新订阅的情况下重置主题,则可以执行以下操作:

import { Observable, Subject } from "rxjs";
import { startWith, switchMap } from "rxjs/operators";

function resettable<T>(factory: () => Subject<T>): {
  observable: Observable<T>,
  reset(): void,
  subject: Subject<T>
} {
  const resetter = new Subject<any>();
  const source = new Subject<T>();
  let destination = factory();
  let subscription = source.subscribe(destination);
  return {
    observable: resetter.asObservable().pipe(
      startWith(null),
      switchMap(() => destination)
    ),
    reset: () => {
      subscription.unsubscribe();
      destination = factory();
      subscription = source.subscribe(destination);
      resetter.next();
    },
    subject: source
  };
}

resettable将返回一个包含以下内容的对象:

  • 可重置主题的订户应订阅的observable
  • 您要在其上调用subjectnexterror的{​​{1}};和
  • 一个complete函数,它将重置(内部)主题。

您将像这样使用它:

reset

答案 1 :(得分:1)

如果您可以使用以下事实:缓冲区会消耗原始源中的数据,并且接收到所有旧值后,已缓冲数据的订户可以切换到原始源,则问题将变得更加容易。

例如。

let data$ = new Subject<any>() // Data source

let buffer$ = new ReplaySubject<any>() 
let bs = data$.subscribe(buffer$)  // Buffer subscribes to data

// Observable that returns values until nearest reset
let getRepeater = () => {
   return concat(buffer$.pipe(
      takeUntil(data$), // Switch from buffer to original source when data comes in
    ), data$)
}

要清除,请更换缓冲区

// Begin Buffer Clear Sequence
bs.unsubscribe()
buffer$.complete()

buffer$ = new ReplaySubject()
bs = data$.subscribe(buffer$)
buffObs.next(buffer$)

为使代码更具功能性,您可以将getRepeater()函数替换为反映最新参考的主题

let buffObs = new ReplaySubject<ReplaySubject<any>>(1)
buffObs.next(buffer$)        

let repeater$ = concat(buffObs.pipe(
   takeUntil(data$),
   switchMap((e) => e),                    
), data$)

以下

    let data$ = new Subject<any>()

    let buffer$ = new ReplaySubject<any>()
    let bs = data$.subscribe(buffer$)         

    let buffObs = new ReplaySubject<ReplaySubject<any>>(1)
    buffObs.next(buffer$)        

    let repeater$ = concat(buffObs.pipe(
      takeUntil(data$),
      switchMap((e) => e),                    
    ), data$)

    // Begin Test

    data$.next(1)
    data$.next(2)
    data$.next(3)

    console.log('rep1 sub')
    let r1 = repeater$.subscribe((e) => {          
      console.log('rep1 ' + e)
    })

    // Begin Buffer Clear Sequence
    bs.unsubscribe()
    buffer$.complete()

    buffer$ = new ReplaySubject()
    bs = data$.subscribe(buffer$)
    buffObs.next(buffer$)
    // End Buffer Clear Sequence

    console.log('rep2 sub')
    let r2 = repeater$.subscribe((e) => {
      console.log('rep2 ' + e)
    })

    data$.next(4)
    data$.next(5)
    data$.next(6)

    r1.unsubscribe()
    r2.unsubscribe()

    data$.next(7)
    data$.next(8)
    data$.next(9)        

    console.log('rep3 sub')
    let r3 = repeater$.subscribe((e) => {
      console.log('rep3 ' + e)
    })

输出

rep1子

rep1 1

rep1 2

rep1 3

rep2子

rep1 4

rep2 4

rep1 5

rep2 5

rep1 6

rep2 6

rep3子

rep3 4

rep3 5

rep3 6

rep3 7

rep3 8

rep3 9

答案 2 :(得分:1)

我遇到了同样的问题:我的一个组件订阅了共享服务的ReplaySubject。一旦导航并返回以前的值,这些值仍然传递到组件。 仅完成主题还不够。

上面的解决方案为此目的似乎很复杂,但是我发现了另一个真正简单的解决方案,就是完成主题并在共享服务中分配一个新创建的解决方案,如下所示:

constructor() {
    this.selectedFeatures = new ReplaySubject()
    this.selectedFeaturesObservable$ = this.selectedFeatures.asObservable()
}

completeSelectedFeatures() {
    this.selectedFeatures.complete()
    this.selectedFeatures = new ReplaySubject()
    this.selectedFeaturesObservable$ = this.selectedFeatures.asObservable()

}

我还打印了共享服务的构造函数以显示我使用的类型。 这样,每当我离开组件时,只要在共享服务上调用该方法,就可以在每次导航回使用可观察到的共享服务的组件时获得一个新的,空的ReplaySubject。 我在ngOnDestroy Angular生命周期挂钩中调用该方法:

ngOnDestroy() {
    console.log('unsubscribe')
    this.featureSub.unsubscribe()
    this.sharedDataService.completeSelectedFeatures()
}

答案 3 :(得分:1)

这里有一个类使用了之前贴在这里的resettable factory,所以你可以使用 const myReplaySubject = new ResettableReplaySubject<myType>()

import { ReplaySubject, Subject, Observable, SchedulerLike } from "rxjs";
import { startWith, switchMap } from "rxjs/operators";

export class ResettableReplaySubject<T> extends ReplaySubject<T> {

reset: () => void;

constructor(bufferSize?: number, windowTime?: number, scheduler?: SchedulerLike) {
    super(bufferSize, windowTime, scheduler);
    const resetable = this.resettable(() => new ReplaySubject<T>(bufferSize, windowTime, scheduler));

    Object.keys(resetable.subject).forEach(key => {
        this[key] = resetable.subject[key];
    })

    Object.keys(resetable.observable).forEach(key => {
        this[key] = resetable.observable[key];
    })

    this.reset = resetable.reset;
}


private resettable<T>(factory: () => Subject<T>): {
    observable: Observable<T>,
    reset(): void,
    subject: Subject<T>,
} {
    const resetter = new Subject<any>();
    const source = new Subject<T>();
    let destination = factory();
    let subscription = source.subscribe(destination);
    return {
        observable: resetter.asObservable().pipe(
            startWith(null),
            switchMap(() => destination)
        ) as Observable<T>,
        reset: () => {
            subscription.unsubscribe();
            destination = factory();
            subscription = source.subscribe(destination);
            resetter.next();
        },
        subject: source,
    };
}
}