如何获取RxJS Subject或Observable的当前值?

时间:2016-05-07 14:54:36

标签: javascript angular rxjs

我有一个Angular 2服务:

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {
  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
  }
}

一切都很好。但我有另一个不需要订阅的组件,它只需要在某个时间点获取isLoggedIn的当前值。我怎么能这样做?

11 个答案:

答案 0 :(得分:238)

SubjectObservable没有当前值。当一个值被发出时,它会被传递给订阅者,Observable就完成了。

如果您想拥有当前值,请使用专为此目的而设计的BehaviorSubjectBehaviorSubject保留最后发布的值并立即将其发送给新订阅者。

它还有一个方法getValue()来获取当前值。

答案 1 :(得分:102)

应该获取价值的唯一方式&#34;&#34;观察者/主体是订阅者!

如果你正在使用getValue(),你就会在声明范式中做一些必要的事情。它在那里作为逃生舱,但 99.9%的时间你不应该使用getValue() getValue()会做一些有趣的事情:它如果主题已被取消订阅将会引发错误,如果主题因为错误等原因而死亡,则会阻止您获取值。但是,再次,它会作为罕见的逃脱舱口出现情况。

有几种方法可以在&#34; Rx-y&#34;中获取主题或观察者的最新值。方式:

  1. 使用BehaviorSubject:但实际订阅。当您第一次订阅BehaviorSubject时,它将同步发送它收到的或之前初始化的值。
  2. 使用ReplaySubject(N):这会缓存N值并将其重播给新订阅者。
  3. A.withLatestFrom(B):当可观察B发出时,使用此运算符从可观察的A获取最新值。将在数组[a, b]中为您提供这两个值。
  4. A.combineLatest(B):每当AB发出时,使用此运算符即可从AB获取最新值。将为您提供数组中的两个值。
  5. shareReplay():通过ReplaySubject进行可观察多播,但允许您在出错时重试observable。 (基本上它为您提供了承诺-y缓存行为)。
  6. publishReplay()publishBehavior(initialValue)multicast(subject: BehaviorSubject | ReplaySubject)等:利用BehaviorSubjectReplaySubject的其他运营商。同一事物的不同风格,它们基本上通过汇集通过主题的所有通知来多播源可观察。您需要致电connect()订阅主题来源。

答案 2 :(得分:5)

我有类似的情况,其后期订阅者在其价值到达后订阅了主题。

我发现ReplaySubject类似于BehaviorSubject就像在这种情况下的魅力一样。 以下是更好解释的链接:http://reactivex.io/rxjs/manual/overview.html#replaysubject

答案 3 :(得分:3)

我在子组件中遇到了同样的问题,最初它必须具有Subject的当前值,然后订阅Subject来监听更改。我只是维护服务中的当前值,以便组件可以访问,例如:

import {Storage} from './storage';
import {Injectable} from 'angular2/core';
import {Subject}    from 'rxjs/Subject';

@Injectable()
export class SessionStorage extends Storage {

  isLoggedIn: boolean;

  private _isLoggedInSource = new Subject<boolean>();
  isLoggedIn = this._isLoggedInSource.asObservable();
  constructor() {
    super('session');
    this.currIsLoggedIn = false;
  }
  setIsLoggedIn(value: boolean) {
    this.setItem('_isLoggedIn', value, () => {
      this._isLoggedInSource.next(value);
    });
    this.isLoggedIn = value;
  }
}

需要当前值的组件才能从服务中访问它,即:

sessionStorage.isLoggedIn

不确定这是否是正确的做法:)

答案 4 :(得分:1)

一个类似外观的答案被否决了。但我认为我可以证明我在有限情况下的建议。


虽然可观察对象确实没有当前值,但通常会具有立即可用值。例如,对于redux / flux / akita存储库,您可以根据多个可观察对象从中央存储库请求数据,并且通常可以立即获得该值。

如果是这种情况,那么当您subscribe时,该值将立即返回。

因此,假设您有一个服务调用,并且希望在完成后要从商店中获取可能不会散发出来的商品的最新价值:

您可以尝试执行此操作(并且应尽可能将内容保留在“管道内”):

 serviceCallResponse$.pipe(withLatestFrom(store$.select(x => x.customer)))
                     .subscribe(([ serviceCallResponse, customer] => {

                        // we have serviceCallResponse and customer 
                     });

问题在于,它将阻塞直到次要可观察对象发出一个值,该值可能永远不会出现。

我发现自己最近只需要评估一个可观察的,只要有一个值立即可用,更重要的是,我需要能够检测出它是否可用。我最终这样做:

 serviceCallResponse$.pipe()
                     .subscribe(serviceCallResponse => {

                        // immediately try to subscribe to get the 'available' value
                        // note: immediately unsubscribe afterward to 'cancel' if needed
                        let customer = undefined;

                        // whatever the secondary observable is
                        const secondary$ = store$.select(x => x.customer);

                        // subscribe to it, and assign to closure scope
                        sub = secondary$.pipe(take(1)).subscribe(_customer => customer = _customer);
                        sub.unsubscribe();

                        // if there's a delay or customer isn't available the value won't have been set before we get here
                        if (customer === undefined) 
                        {
                           // handle, or ignore as needed
                           return throwError('Customer was not immediately available');
                        }
                     });

请注意,对于上述所有情况,我都使用subscribe来获取值(如@Ben所讨论)。即使我有.value,也不使用BehaviorSubject属性。

答案 5 :(得分:1)

const observable = of('response')

function hasValue(value: any) {
  return value !== null && value !== undefined;
}

function getValue<T>(observable: Observable<T>): Promise<T> {
  return observable
    .pipe(
      filter(hasValue),
      first()
    )
    .toPromise();
}

const result = await getValue(observable)
// Do the logic with the result
// .................
// .................
// .................

您可以从此处查看有关如何实现它的完整文章。 https://www.imkrish.com/how-to-get-current-value-of-observable-in-a-clean-way/

答案 6 :(得分:0)

尽管听起来可能有些夸大其词,但这只是保持 Observable 类型并减少样板的另一种“可能”解决方案...

您总是可以创建一个扩展获取器来获取Observable的当前值。

为此,您需要在Observable<T>类型声明文件中扩展global.d.ts接口。然后在observable.extension.ts文件中实现 extension getter ,最后将类型和扩展文件都包含到您的应用程序中。

您可以参考此StackOverflow Answer来了解如何将扩展包含在Angular应用程序中。

// global.d.ts
declare module 'rxjs' {
  interface Observable<T> {
    /**
     * _Extension Method_ - Returns current value of an Observable.
     * Value is retrieved using _first()_ operator to avoid the need to unsubscribe.
     */
    value: Observable<T>;
  }
}

// observable.extension.ts
Object.defineProperty(Observable.prototype, 'value', {
  get <T>(this: Observable<T>): Observable<T> {
    return this.pipe(
      filter(value => value !== null && value !== undefined),
      first());
  },
});

// using the extension getter example
this.myObservable$.value
  .subscribe(value => {
    // whatever code you need...
  });

答案 7 :(得分:0)

执行此操作的最佳方法是使用Behaviur Subject,下面是一个示例:

var sub = new rxjs.BehaviorSubject([0, 1])
sub.next([2, 3])
setTimeout(() => {sub.next([4, 5])}, 1500)
sub.subscribe(a => console.log(a)) //2, 3 (current value) -> wait 2 sec -> 4, 5

答案 8 :(得分:0)

可以创建订阅,并且在删除第一个发出的项之后将其创建。管道是使用Observable作为其输入并返回另一个Observable作为输出的函数,而不修改第一个Observable。角8.1.0。软件包:"rxjs": "6.5.3""rxjs-observable": "0.0.7"

  ngOnInit() {

    ...

    // If loading with previously saved value
    if (this.controlValue) {

      // Take says once you have 1, then close the subscription
      this.selectList.pipe(take(1)).subscribe(x => {
        let opt = x.find(y => y.value === this.controlValue);
        this.updateValue(opt);
      });

    }
  }

答案 9 :(得分:0)

有两种方法可以实现这一点。

  1. BehaviorSubject 有一个方法 getValue(),您可以通过它获取特定时间点的值。

  2. 您可以直接使用 BehaviorSubject 进行订阅,并且可以将订阅的值传递给类成员、字段或属性。

我不推荐这两种方法。

在第一种方法中,这是一种方便的方法,您可以随时获取该值,您可以将其称为该时间点的当前快照。问题在于您可以在代码中引入竞争条件,您可能会在许多不同的地方和不同的时间调用此方法,这很难调试。

第二种方法是大多数开发人员在订阅时需要原始值时采用的方法,您可以跟踪订阅以及何时完全取消订阅以避免进一步的内存泄漏,如果您真的很想绑定它,则可以使用此方法到一个变量,没有其他方法可以连接它。

我建议,再看看你的用例,你在哪里使用它?比如你想在调用任何API时判断用户是否登录,可以结合其他observables:

const data$ = apiRequestCall$().pipe(
 // Latest snapshot from BehaviorSubject.
 withLatestFrom(isLoggedIn),
 // Allow only call if loggedin.
 filter(([request, loggedIn) => loggedIn)
 // Do something else..
)

有了这个,您可以通过管道 data$ | async 将其直接用于 UI,以防有角度。

答案 10 :(得分:-1)

您可以将最后一个发射值与Observable分开存储。然后在需要时阅读。

let lastValue: number;

const subscription = new Service().start();
subscription
    .subscribe((data) => {
        lastValue = data;
    }
);